java面向对象编程(高级)

static 关键字

如果想让一个成员变量或方法被类的所有实例所共享,就用 static 修饰即可,称为类变量、类方法或类属性、静态变量

使用范围:属性方法代码块内部类

被修饰后具备的特点:

  1. 随着类的加载而加载
  2. 优先于对象存在
  3. 被所有对象所共享
  4. 访问权限允许时,可不创建对象,直接被类调用

static 成员变量和方法

静态变量

使用 static 修饰的成员变量就是静态变量(或类变量、类属性)

public class Goods{
  // 实例变量
  String name;
  double price;
  // 类变量,静态变量
 	static String brand = "优乐美";
  public Goods() {}
  public Goods(String name) {
    this.name = name;
  }
}

public calss GoodsTest{
  public static void main(String[] args){
    Goods goods1 = new Goods("xm");
    Goods goods2 = new Goods("xn");
    
    // 都输出 “优乐美”
    System.out.println(goods1.brand);
    System.out.println(goods2.brand);
    System.out.println(Goods.brand); // 直接类调用
    
    // 改变了,都输出 “哇哈哈”,因为变量的值是共享的
    goods1.brand = "哇哈哈";
    System.out.println(goods1.brand);
    System.out.println(goods2.brand);
    System.out.println(Goods.brand); // 直接类调用
    
  }
}

静态变量的特点:

  1. 默认值规则与实例变量一样
  2. 静态变量值所有对象共享
  3. 静态变量在本类中,可以在任意方法、代码块、构造器中直接使用
  4. 如果权限修饰符允许,在其他类中可以通过类名.静态变量直接访问,也可以通过 对象.静态变量的方式访问
  5. 静态变量的 get/set 方法也静态的,当局部变量与静态变量重名时,使用类名.静态变量进行区分

静态方法

用 static 修饰的成员方法就是静态方法

public class Father {
  static String name = "Father";
  public static void method(){
    // 方法内部只能访问类的 static 修饰的属性或方法
    System.out.println( name + "method");
  }
}

public class Son extends Father {
  // @Override 
  // 尝试重写静态方法,加上@Override 编译报错,
  // 去掉 Override不报错,但是也不是重写
  public static void method() {
    System.out.println("son");
  }
}
public class staticTest {
  public static void main(String[] args) {
    // 直接类调用
    Father.method(); // Father method
    Son.method(); // 子类的static方法 son
    
    // 执行 Father 类中的 method
    Father f = new Son();
    f.method();  // Father method
  }
}

单例模式

对某个类只能存在一个实例对象,而且只提供一个取得该类对象实例的方法

实现思路:

  1. 将类的构造器访问权限设为private ,外部就不能 new 产生对象了
  2. 设一个静态方法,返回类的内部创建的实例
  3. 成员变量和内部创建实例的变量都设为静态

单例模式的两种实现方式

饿汉式

class Singleton {
  // 私有化构造器
  private Singleton(){}
  // 内部创建实例,设为static
 	private static Singleton singLeton = new Singleton();
  // 通过公共的静态方法 返回当前类的对象
  public static Singleton getInstance(){
    return singleton;
  }
}

特点

  • 立即加载,使用类时,对象已经创建完成
  • 没有多线程安全问题,实现起来简单
  • 耗费内存,类加载时,静态变量被创建并且分配内存空间,此后一直占用这块内存空间,直到类销毁 / 卸载

懒汉式

class Singleton{
  // 私有化构造器
  private Singleton(){}
  // 内部提供实例,设为static
  private static Singleton singLeton;
  public static Singleton getInstance(){
    if(singLeton==null) {
      singLeton = new Singleton;
    }
    return singLeton;
  }
}

特点

  • 延迟加载,调用类的静态方法时对象才创建
  • 节省内存,在调用方法才创建且分配空间,一定程度上节省了内存空间
  • 线程不安全,不能保证单例的唯一性,在多线程环境下这种实现方法是错误的

应用场景

  • Windows 的 Task Manager (任务管理器)就是很典型的单例模式
  • Windows 的 Recycle Bin (回收站)也是典型的单例应用。在整个系统运行过程中,回收 站一直维护着仅有的一个实例
  • •Application 也是单例的典型应用
  • 应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直 处于打开状态,因为只能有一个实例去操作,否则内容不好追加
  • 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源

理解 main 方法的语法

public static void main(String[] args){}
  • JVM 要调用 main 方法 ,权限修饰符为 public
  • 执行 main 方法时不需要创建对象所以用 static 修饰
  • 接收一个 String 类型的数组参数,数组中保存执行 Java 命令时传递给类的参数

例如

public class mainTest {
   public static void main(String[] args){
     for(int i = 0; i< args.length; i++){
       System.out.println("args[" + i + "] = " + args[i]);
     }
   }
} 

命令行执行

java mainTest "fantasy" "jay" "yxl"

输出结果

args[0] = fantasy args[1] = jay args[2] = yxl

代码块

代码块的作用:对 java 类或对象初始化

一个类中代码块若有修饰符,则只能被 static 修饰,称为静态代码块(static block),没有修饰的是非静态代码块

静态代码块

public class Cat {
  private String name;
  private static int leg;   
  static {
    leg = 4;
    System.out.printIn("静态代码块");
  }
}

总结:

  1. 可以有输出语句
  2. 可以对类的属性、声明进行初始化操作
  3. 不可以调用非静态方法和属性
  4. 如果有多个静态代码块,按从上往下顺序执行
  5. 执行顺序优先于非静态代码块
  6. 随着类的加载而加载,只执行一次

非静态代码块

意义:如果有多个构造器重载,而且里面有公共代码,即可提取到 非静态代码块里,减少冗余代码,因为优先于构造执行的

public class User {
  private String userName;
  private String passWord;
  private long registrationTime;
  {
    // 创建 User 对象都会执行代码块里的代码
    System.out.printIn("新用户注册");
    registrationTime = System.currentTimeMillis();
  }
  public User(){
    userName = registrationTime+"";
    passWord = "123456";
  }
  public User(String userName, String passWord){
    this.userName = userName;
    this.passWord = passWord;
  }
}

特点:

  1. 可以有输出语句
  2. 可以对类的属性、类的声明进行初始化操作
  3. 除了调用非静态的结构外,还可以调用静态的变量或方法
  4. 若有多个非静态的代码块,那么按照从上到下的顺序依次执行
  5. 每次创建对象的时候,都会执行一次。且先于构造器执行

final 关键字

final 表示最终的不可更改的

final 修饰类

表示类不可继承,没有子类,提高安全性,程序的可读性

例如:String 类、System 类、StringBuffer 类

final 修饰变量和方法

方法:表示方法不可以被子类重写,例如,Object 类中的 getClass()

变量:表示赋值后不可更改,即常量,变量名通常大写字母表示,例如,final double MY_PI = 3.14

注意:如果某个成员变量用 final 修饰后,没有 set 方法,并且必须初始化(可以显式 赋值、或在初始化块赋值、实例变量还可以在构造器中赋值)

abstract 关键字

类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类

抽象方法

在父类声明的方法时没有方法体,只有方法签名的方法就是抽象方法

包含抽象方法的类必须是抽象类

使用 abstract 修饰的类 / 方法 就是抽象类 / 方法

例如

public abstract class Person{
  public abstract void eat();
}

抽象方法的实现:子类继承抽象类重写类中的抽象方法

public class Student extends Person {
  public void eat(){
    System.out.printIn("吃饱喝好!!!");
  }
}

总结

  • 抽象类是不能创建对象的,是用来被子类继承的

  • 抽象类的子类必须重写全部抽象方法,并且提供方法体

  • 抽象类中,也有构造方法,是供子类创建对象时,初始化父类成员变量使用的

  • 抽象类中不一定有抽象方法,但有抽象方法必须是抽象类

interface 接口

接口就是规范,定义的是一组规则,体现了现实世界中 “如果你是/要…则必须 能…” 的思想。继承是一个 “是不是” 的 is-a 关系,而接口实现则是 “能不能” 的 has-a 关系

接口的本质是契约、标准、规范,就像我们的法律一样。制定好后大 家都要遵守

定义格式

关键字 interface ,与定义类的方式相同,它并不是类,而是另外一种引用数据类型

public interface Student {
  // 静态常量 通常采用大写
  // public static final 可以省略
  long CURRENT_TASK = "学习";
  // 抽象方法
  // public abstract 可以省略
  void wirte();
  void read();
  
  // 默认方法 JDK8
  default void attendClass() {
    System.out.printIn("学生需要上课");
  }
  
  // 静态方法  JDK8
  static void eat() {
    System.out.printIn("学生需要吃饱喝好");
  }
} 

实现接口

接口不能创建对象,但是可以被类实现 关键字 implements

类与接口的关系为实现关系,即类实现接口,该类就被称为接口的实现类

public class xm implements Student {
  // ...重写接口的抽象方法
}
// 继承 + 实现
public class xh extends Person implements Student {
  // ...
}

注意:

  • 实现接口的类非抽象类,必须重写接口中的所有抽象方法
  • 接口的默认方法,实现类可以选择重写(重写时不用保留 default)
  • 接口的静态方法不能重写也不能被继承

接口的多实现

一个类只能继承一个父类,而对于接口而言,一个 类是可以实现多个接口的,这叫做接口的多实现

【修饰符】 class 实现类 implements 接口 1,接口 2,接口 3... {}
【修饰符】 class 实现类 extends 父类 implements 接口 1,接口 2...{}

注:如果抽象方法有重名的只需要重写一次

接口的多继承

一个接口可以继承另一个或者多个接口,接口继承关键字也是 extends

// 父类
public interface Person {
  void sleep();
  void eat();
}
public interface Motion {
  void run();
  void basketball();
}

// 子接口
public interface Student extends Person,Motion {
  void read();
}

// 实现类
public class xm implements Student {
  void sleep(){
    System.out.println("睡觉");
  }
  void eat(){
    System.out.println("吃东西");
  }
  void run(){
    System.out.println("跑步");
  }
  void basketball(){
    System.out.println("篮球");
  }
  void read(){
    System.out.println("读书");
  }
}

所有父接口的抽象方法都有重写, 方法签名相同的抽象方法只需要实现一次

接口与实现类对象构成多态引用

接口类型的变量与实现类的对象之间,也可以构成多态引用

通过接口类型的变量调用方法,最终执行的是 你 new 的实现类对象实现的方法体

public interface Motion {
  void run();
}
public class Cat implements Motion {
  public void(){
     System.out.println("猫会跑");
  }
}

public class Dog implements Motion {
  public void(){
     System.out.println("狗会跑");
  }
}

// 测试
public class TestInterface{
  public static void main(String[] args){
    Motion c = new Cat();
    c.run();
    
    Motion d = new Dog();
    d.run;
  }
}

接口的非静态方法

  • 对于接口的静态方法,直接使用“接口名.”进行调用即可

    • 也只能使用“接口名.”进行调用,不能通过实现类的对象进行调用
  • 对于接口的抽象方法、默认方法,只能通过实现类对象才可以调用

    • 接口不能直接创建对象,只能创建实现类的对象

总结

  1. 接口本身不能创建对象,只能创建接口的实现类对象,接口类型的变量可以与实现类 对象构成多态引用

  2. 声明接口用 interface,接口的成员声明有限制:

    • 公共的静态常量

    • 公共的抽象方法

    • 公共的默认方法(JDK8.0 及以上)

    • 公共的静态方法(JDK8.0 及以上)

    • 私有方法(JDK9.0 及以上)

  3. 类可以实现接口,关键字是 implements,而且支持多实现

    • 如果实现类不是抽象类, 就必须实现接口中所有的抽象方法

    • 如果实现类既要继承父类又要实现父接口,那么 继承(extends)在前,实 现(implements)在后

  4. 接口可以继承接口,关键字是 extends,而且支持多继承

  5. 接口的默认方法可以选择重写或不重写。如果有冲突问题,另行处理。子类重写父接 口的默认方法,要去掉 default,子接口重写父接口的默认方法,不要去掉 default

  6. 接口的静态方法不能被继承,也不能被重写。接口的静态方法只能通过 “接口名.静态 方法名” 进行调用

接口和抽象类的对比

内部类

将一个 类A 定义在 另一个类B 里面,类A就被称为内部类,类B为外部类

内部类的分类

  1. 成员内部类
    • 静态成员内部类
    • 非静态成员内部类
  2. 局部内部类
    • 匿名局部内部类
    • 非匿名局部内部类

成员内部类

举例

public class Outer {
  private static String a ="静态外部类的静态 a";
  private String b = "非静态外部类 b"
  // 创建静态内部类实例
  static class StaticInner {
    private static String a ="静态内部类的静态 a";
    public static void show(){
      System.out.printIn(this.a);
      System.out.printIn(Outer.a);
      // 不能访问外部类的非静态成员 
      // System.out.printIn(Outer.b);
    }
  }
  // 创建非静态内部类实例
  class NOStaticInner {
    private String a ="非静态内部类的非静态 a";
    System.out.printIn(this.a);
    System.out.printIn(Outer.a);
    System.out.printIn(Outer.b);
  }
}


// 实例化
public class TestInnerClass {
  public static void main(String[] args) {
    //创建静态内部类实例
    Outer.StaticInner innter = new Outer.StaticInner();
    //调用静态内部类静态方法
 		Outer.StaticInner.show();
    
    // 创建非静态内部类实例
    Outer outer = new Outer();
    Outer.NoStaticInner inner1 = outer.new NoStaticInner()
  }
} 

总结

  1. 内部类作为类的成员,可以声明为 private 或 protected,调用外部类的结构,声明为 static 的,但此时就不能使用外层类的非 static 的 成员变量
  2. 内部类作为类的角色,可以定义属性、方法、构造器等结构,继承父类,实现接口,声明为 abstract 抽象类,声明 final

局部内部类

举例:

public class Outer {
  public static void outerMethod(){
    class Inner{
      public void innerMethod(){
        System.out.printIn("局部内部类方法")
      }
    }
    Inner inner = new Inner();
    inner.innerMethod();
  }
}
  1. 和成员内部类不同的是,它前面不能有权限修饰符等
  2. 和局部变量一样,有作用域
  3. 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名、$符号、 编号

匿名内部类

举例:使用匿名内部类对象直接调用方法

interface A {
  public void show();
}
public class TestInnerClass(){
  public static void main(String[] args) {
    new hello(){
      @Override
      public void show(){
        System.out.printIn("hello world");
      }
    }.show();
  }
}

举例:通过父类或接口的变量多态引用匿名内部的对象

interface A {
  public void show();
}
public class TestInnerClass(){
  public static void main(String[] args) {
   A a =  new A(){
      @Override
      public void show(){
        System.out.printIn("hello world");
      }
    }
    a.show();
  }
}

举例:匿名内部类的对象作为实参

interface A {
  public void show();
}
public class TestInnerClass(){
  public static testMethod(A a){
    a.show();
  }
  public static void main(String[] args) {
		testMethod(new A{
      @Override
      public void show(){
        System.out.printIn("hello world");
      }
    })
  }
}

枚举类

枚举类本质上也一个类,只不过类的对象有限,固定好,不能让用户随意更改

enum 关键字声明枚举 ,JDK5.0 之后

举例:方向,上下左右

public enum Direction {
  LETF,RIGHT,TOP,BOTTOM;
}

public class TestEnum {
  public static void main(String[] args) {
    Direction top = Direction.TOP;
  }
}

举例:季节,春夏秋冬

public enum Season{
  SPRING("春天","春风又绿江南岸"),
  SUMMER("夏天","映日荷花别样红"),
  AUTUMN("秋天","秋水共长天一色"),
  WINTER("冬天","窗含西岭千秋雪");
  
  private final String seasonName;
  private final String seasonDesc;
  
  private Season(String seasonName, String seasonDesc){
    this.seasonName = seasonName;
    this.seasonDesc = seasonDesc;
  }
  
  public String getSeasonName() {
    return seasonName;
  }
  public String getSeasonDesc() {
    return seasonDesc;
  }
}

总结

  1. 枚举类的常量对象列表必须在枚举类的首行,常量规范大写
  2. 实例系统会自动添加 public static final 修饰
  3. 常量后面没有代码可以省略 ; ,否则必须添加上
  4. 默认提供的是 private 的无参构造
  5. 需要有参构造,手动添加,private 可以省略,调用时,常量对象名(实参列表)
  6. 默认继承的是 java.lang.Enum 类,因此不能再继承其他的类型
  7. JDK5.0 之后 switch,提供支持枚举类型,case 后面可以写枚举常量名,无需添加枚 举类作为限定

常用方法

  • String toString():默认返回的是常量名(对象名),可以继续手动重写该方法
  • static 枚举类型[] values():返回枚举类型对象的数值,为静态方法
  • static 枚举类型 valueOf(String name):把一个字符串转为枚举类对应的对象,如果字符串不是,会有运行时异常:IllegalArg umentException
  • String name():获取枚举常量的名字
  • int ordinal():返回当前枚举常量的次序号,从 0 开始

实现接口

  • 如果每一个枚举值都需要执行同一个方法行为,统一实现接口的方法即可
  • 如果每一个枚举值都需要执行同一个方法不同行为,则让每个枚举值分别来实现该方法
interface Info{
void show();
}
// 统一实现,直接重写方法即可
enum Direction1 implements Info {
    LETF,RIGHT,TOP,BOTTOM;
    @Override
    public void show() {
    	System.out.printIn("Direction");    
    }
}
// 枚举类的每一个对象重写接口中的抽象方法
// 执行的是不同的实现的方法
public enum Direction implements Info{
    LEFT{
        @Override
        public void show() {
            System.out.println("左边");
        }
    },RIGHT {
        @Override
        public void show() {
            System.out.println("右边");
        }
    },TOP {
        @Override
        public void show() {
            System.out.println("上边");
        }
    },BOTTOM {
        @Override
        public void show() {
            System.out.println("下边");
        }
    };

}

注解

注解(Annotation)是从 JDK5.0 开始引入,以@注解名在代码中存在

注解可以像修饰符一样被使用,可用于修饰包、类、构造器、方法、成员变量、参数、局部变量的声明。可添加一些参数值,这些信息被保存在 Annotation 的 name=value 对中

注解可以在类编译、运行时进行加载,体现不同的功能

常用注解

生成文档相关的注解

  • @author标明开发该类模块的作者,多个作者之间使用,分割
  • @version 标明该类模块的版本
  • @see 参考转向,也就是相关主题
  • @since 从哪个版本开始增加的
  • @param 对方法中某参数的说明,如果没有参数就不能写
  • @return 对方法返回值的说明,如果方法的返回值类型是 void 就不能写
  • @exception 对方法可能抛出的异常进行说明 ,如果方法没有用 throws 显式抛出的 异常就不能写

在编译时进行格式检查(JDK 内置的三个基本注解):

  • @Override: 限定重写父类方法,该注解只能用于方法
  • @Deprecated: 用于表示所修饰的元素(类,方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择
  • @SuppressWarnings: 抑制编译器警告

@Override

  • 用于检测被标记的方法为有效的重写方法,如果不是,则报编译错误
  • 只能标记在方法上
  • 它会被编译器程序读取

@Deprecated

  • 用于表示被标记的数据已经过时,不推荐使用
  • 可以用于修饰 属性、方法、构造、类、包、局部变量、参数
  • 它会被编译器程序读取

@SuppressWarnings

  • 抑制编译警告。当我们不希望看到警告信息的时候,可以使用 SuppressWarnings 注 解来抑制警告信息
  • 可以用于修饰类、属性、方法、构造、局部变量、参数
  • 它会被编译器程序读取

元注解

元注解就是注解到其他注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其他的注解上面

JDK1.5 在 java.lang.annotation 包定义了 4 个标准的 meta-annotation 类型,它 们被用来提供对其它 annotation 类型作说明

@Target

用于描述注解的使用范围,通过枚举类型 ElementType 的 10 个常量对象来指定范围

@Retention

用于描述注解的生命周期,通过枚举类型 RetentionPolicy 的 3 个常量对象来指定

SOURCE(源代码)、CLASS(字节码)、RUNTIME(运行时)

@Documented

表明这个注解应该被 javadoc 工具记录

@Inherited

允许子类继承父类中的注解

自定义注解

一个完整的注解应该包含三个部分: (1)声明 (2)使用 (3)读取

【元注解】
【修饰符】 @interface 注解名{
 【成员列表】
}

举例

@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface myAnnotation {
  String value();
}

@Inherited
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
  String columnName();
  String columnType();
}
  1. 自定义注解可以通过四个元注解@Retention,@Target,@Inherited,@Documented,分别说明它的声明周期,使用位置,是否被继承,是否被生成到 API 文档中。
  2. Annotation 的成员在 Annotation 定义中以无参数有返回值的抽象方法的形式来声
    明,我们又称为配置参数。返回值类型只能是八种基本数据类型、String 类型、Class
    类型、enum 类型、Annotation 类型、以上所有类型的数组
  3. 可以使用 default 关键字为抽象方法指定默认返回值
  4. 如果定义的注解含有抽象方法,那么使用时必须指定返回值,除非它有默认值。格式
    方法名 = 返回值,如果只有一个抽象方法需要赋值,且方法名为 value,可以省
    value=,所以如果注解只有一个抽象方法成员,建议使用方法名 value

JUnit 单元测试

JUnit 是由 Erich Gamma 和 Kent Beck 编写的一个测试框架(regression testing framework),供 Java 开发人员编写单元测试之用

导入 JUnit 单元测试后 ,测试代码无需创建 main 方法 ,声明静态方法…,在方法添加 @Test 即可运行此方法

import org.junit.Test;
public class TestJUnit {
  @Test
  public void test01(){
    System.out.println("TestJUnit.test01");
  }
  @Test
  public void test02(){
    System.out.println("TestJUnit.test02");
  }
}

JUnit4 版本,要求@Test 标记的方法必须满足如下要求:

  • 所在的类必须是 public 的,非抽象的,包含唯一的无参构造器
  • @Test 标记的方法本身必须是 public,非抽象的,非静态的,void 无返回值,()无参 数的。

包装类

Java 针对八种基本数据类型定义了相应的引用类型:包装类(封装类)

基本数据类型 包装类
byte Byet
short Short
int Integer
long Long
float Float
double Double
boolean Boolean
char Character

除了 int char 其他基本数据类型的包装类都是首字母大写

基本数据类型和包装类转化

基本数据类型转包装类(装箱)

// 构造函数
Integer num1 = new Integer(10);
Double num2 = new Double(10.2);

// 包装类的 valueOf 静态方法
Integer num3 = Integer.valueOf(36);

包装类转基本数据类型(拆箱)

Integer num3 = Integer.valueOf(36);
int num4 = num3.intValue();
Double f2 = Double.valueOf(10.2);
float f3 = f2.floatValue();

自动转化(自动装箱与拆箱)

从 JDK5.0 开始,基本类型与 包装类的装箱、拆箱动作可以自动完成

// 自动装箱
// 相当于 Integer i = Integer.valueOf(4);
Integer i = 10;

// 等号右边:将i对象转成基本数值(自动拆箱) 
// 相当于 i.intValue() + 5 
// 运算完成后再次装箱,把基本数值转成对象
i = i + 5;

基本数据类型、包装类与字符串间的转换

基本数据类型转为字符串

int a = 10;
// 方式 1:调用字符串重载的 valueOf()方法
String str = String.valueOf(a);
// 方式 2:运输符拼接字符串
String str = a + "";

字符串转为基本数据类型

方式 1:除了 Character 类之外,其他所有包装类都具有 parseXxx 静态方法可 以将字符串参数转换为对应的基本类型

String str = "10";
int num = Integer.parseInt(str);

方式 2:字符串转为包装类,然后可以自动拆箱为基本数据类型

String str = "10";
int num = Integer.valueOf(str);

方式 3:通过包装类的构造器实现

int a = new Integer("123");
double d = new Double("123.55");

参考资料

尚硅谷Java基础