<Java>9 面向对象编程(高级)

本文最后更新于:2023年6月27日 上午

9 面向对象编程(高级)

9.1 类变量和类方法

9.1.1 类变量

类变量:也叫 静态变量/静态属性。是该类所有对象共享的变量。任何一个该类对象访问时都是相同的值,任何一个该类对象修改时也是同一个变量。

语法(推荐):访问修饰符 static 数据类型 变量名;

或者也可以:static 访问修饰符 数据类型 变量名;

根据 JDK 版本的不同,类变量存放在 堆 中或 方法区 中。

  1. 什么时候需要用类变量:

    当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量)

  2. 类变量 与 实例变量(普通属性)的区别:

    类变量 是该类所有对象共享的,而 实例变量 是每个对象独享的

  3. 加上 static 称为 类变量 或 静态变量。否则称为 实例变量/普通变量/非静态变量

  4. 静态变量 可以通过 类名.类变量名;对象名.类变量名; 来访问。但 Java 设计者推荐我们用 类名.类变量名; 来访问。(需满足访问权限和范围)

  5. 类变量 是在加载类时就初始化了。所以,没有创建对象实例也能访问。

  6. 类变量 的生命周期是随着 类的加载 开始,随着 类的消亡 而销毁。

  7. 特别地:一个 null 对象也可以访问静态变量 / 静态方法

    public class Test{
        static int n = 0;
        static void met() {
            System.out.println(++n);
        }
        
        public static void main(String[] args){
            Test t = null;
            System.out.println(t.n);			//这样不会报错
            t.met();							//这样也不会报错
        }
    }

9.1.2 类方法

当方法使用 static 修饰后,就是 静态方法。静态方法就能访问静态属性。如果我们不希望创建实例,也能调用方法,这个场合把方法做成静态方法是合适的。开发工具类时就可以如此做。

  1. 类方法和普通方法都是随着类的加载而加载,将结构信息存储在 方法区。
  2. 类方法中不允许使用和对象有关的关键字。所以,类方法没有 thissuper
  3. 类方法可以通过类名调用,也能通过对象名调用。普通方法不能通过类名调用。
  4. 类方法 中只能访问 类变量 或 类方法
  5. 普通方法既可以访问普通方法也可以访问类方法

9.2 理解 main 方法语法

public static void main(String[] args){...}

  1. main 方法 是 JVM 调用的方法。所以该方法的 访问权限 必须为 public

  2. JVM 在执行 main 方法时不必创建对象,所以 main方法 必须为 static

  3. 该方法接收 String 类型的数组参数。该数组中保存执行 Java 命令 时传递给所运行的类的参数。

    工作台中:javac 执行的程序.java

    java 执行的程序 参数1(arg[0]) 参数2(arg[1]) 参数3(arg[2]) ..

    IDEA中怎么做?

  4. main 方法 中,我们可以直接调用 main 方法 所在类的静态方法或静态属性。

    但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例后才能通过该实例访问非静态成员。

9.3 代码块

代码块:又称为 初始化块。属于类中的成员。类似于方法,将逻辑语句封装在方法体中,通过 { } 包围起来。

和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类 显式调用,而是加载类时,或创建对象时 隐式调用。

语法:[修饰符]{代码};

  1. 修饰符 是可选项,可不写。要写的话,只能写 static
  2. 代码块分为两类:
    • 静态代码块:有 static
    • 普通代码块:无 static
  3. 逻辑语句可以为任意的逻辑语句。
  4. ; 可以写,也可以省略。建议写上。
  5. 代码块相当于另一种形式的构造器(构造器的补充机制),可以做初始化操作
  6. 如果多个构造器中都有重复语句,就可以抽取到初始化块中,提高代码复用率。这样,不管用哪个构造器,都会执行代码块。

9.3.1 使用细节

  1. static 代码块:作用是对类进行初始化。随着 类的加载 会且只会执行一次。相对的:普通代码块每创建一个对象就执行一次。
  • **类什么时候被加载? **

    • 创建对象实例时(new)

    • 创建子类对象实例,父类也会加载

    • 使用类的静态成员时(父类也会加载)

    以上情况下类会被加载。加载后不需要再次加载,所以,静态代码块也只会执行一次。

  1. 创建一个对象时,在 一个类里 调用顺序是:

    • 调用静态代码块 和 静态属性初始化。这两者优先级相同,多个存在时按照定义的顺序依次执行。
    • 调用普通代码块 和 普通属性初始化。这两者优先级也相同。
    • 调用构造器。
  2. 构造器 的最前面其实隐含了 super();调用普通代码块。而静态相关的代码块,属性初始化,在类加载时就执行完毕了。

    这样,创建一个对象时,在 有继承关系的多个类里 调用顺序是:

    • 父类 静态代码块 和 静态初始化
    • 子类 静态代码块 和 静态初始化
    • 父类 普通代码块 和 普通初始化
    • 父类 构造器
    • 子类 普通代码块 和 普通初始化
    • 子类 构造器
  3. 静态代码块 只能调用 静态成员。普通代码块 能调用 任意成员。

9.4 单例设计模式

什么是设计模式:设计模式是在大量的实践中总结和理论化后优选的代码结构、编程风格、解决问题的思考方式。设计模式就像是经典的棋谱,免去我们自己再思考和摸索。

单例设计模式:采取一定的方法,保证再整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法

9.4.1 应用实例

后面会学更多,这里先展示两种:饿汉式、懒汉式

#9.4.1.1 饿汉式

步骤如下:

  1. 构造器私有化(防止用户直接 new)

  2. 类的内部创建对象

  3. 向外暴露一个静态的公共方法

  4. 代码实现

    class GF{
           private String name;
        private static GF gf = new GF("萝茵");
           private GF(String name){
               this.name = name;
           }
           public static GF getGF(){
               return gf;
           } 
    }

    对象,通常都是重量级的对象

    有时,我们用不到这个创建的对象,那个场合,会造成资源浪费。

#9.4.1.2 懒汉式

步骤如下:

  1. 构造器私有化

  2. 定义一个静态属性对象

  3. 提供一个静态的公共方法,可以返回对象。如果静态对象为空,则创建对象

  4. 代码实现

      >class GF{
       private String name;
    private static GF gf;
       private GF(String name){
           this.name = name;
       }
       public static GF getGF(){
           if(gf == null){
               gf = new GF("萝茵");
           }
           return gf;
       } 
      >}

#9.4.1.3 两种方法对比

  1. 二者创建对象的时机不同。饿汉式在加载类信息时创建,懒汉式在使用时才创建
  2. 饿汉式可能造成资源浪费,懒汉式可能存在线程安全问题(学习[线程]后会进行完善)。
  3. Java SE 标准类中 java.lang.Runtime 就是一个单例模式。

9.5 final 关键字

final 可以修饰 类、属性、方法、局部变量

以下情况下,可能用到 final

  1. final 修饰类:该类不能被继承
  2. final 修饰方法:该方法不能被重写
  3. final 修饰值:该值不能被修改

9.5.1 使用细节

  1. final 修饰的属性又叫常量,一般用 XX_XX_XX 来命名(全大写字母+下划线)

  2. final 修饰的属性在定义时,必须赋初始值,且之后不能再修改。赋值可以在下列位置之一:

    • 定义时
    • 构造器中
    • 代码块中

    注意:如果 final 修饰的属性是静态的,则只能在以下位置赋值。

    • 定义时
    • 静态代码块中
  3. final 类不能继承,但能实例化对象。对的,是可以的。

  4. 如果不是 final 类,但含有 final 方法,虽然该方法不能重写,但能被继承。

  5. final 类可以有 final 方法。可以,但没必要。

  6. final 不能修饰构造方法。

  7. finalstatic 搭配使用,效率更高(那个场合,虽然顺序不限,还是推荐 static 在前)。底层编译器做了优化处理。这样做,调用 属性(定义时赋值) 时居然 不会造成类的加载!

  8. 包装类(Integer、Double、Float、Boolean、String等)都是 final 类,都不能被继承。

9.6 抽象类

当父类的某些方法需要声明,却不知道如何实现时,可以将其声明为抽象方法。那个场合,要将该类声明为 abstract 类。

抽象类的价值更多是用于设计。设计者设计好后,让子类继承并实现。也是考官爱问的考点。

定义抽象类:访问修饰符 abstract 类名{...}

定义抽象方法(注意:无方法体):访问修饰符 abstract 返回值 方法名(形参列表);

9.6.1 使用细节

  1. 抽象类不能被实例化
  2. 抽象类不一定包含抽象方法。也就是说,抽象类可以没有 abstract方法
  3. 一旦包含 abstract 方法,则该类一定要声明为 abstract
  4. abstract 只能修饰 类 和 方法,不能修饰其他。
  5. 抽象类可以有任意成员(非抽象方法、构造器、静态属性等)。即,抽象类本质还是类。
  6. 抽象方法不能有主体。即,抽象方法不能实现。
  7. 如果一个类继承了 abstract 类,则其必须实现所有 abstract 方法,除非其自己也是 abstract 类。
  8. 抽象方法不能用 private final static 来修饰。因为,这些关键词都和 重写 相违背。

9.6.2 模板设计模式

9.7 接口

接口就是给出一些没有实现的方法,封装到一起,到某个类要用的时候,再根据具体情况把这些方法写出来。

语法:interface 接口名{...}

class 类名 implements 接口名{...必须实现接口的抽象方法...}

注意:JDK 7.0 以前,接口中只能是抽象方法。而 JDK 8.0 后,接口可以有静态(static)方法、默认(default)方法。

在接口中,抽象方法可以省略 abstract

接口中可以存在:

  • 属性(只有静态 static 属性,可以不加 static 关键字)
  • 方法(抽象 abstract 方法、默认 default 实现方法、静态 static 方法)

9.7.1 使用细节

  1. 接口 不能被实例化。
  2. 接口中所有方法都是 public 方法。接口中的 抽象方法 可以不用 abstract 修饰。
  3. 一个普通类实现接口,就必须把该接口所有方法都实现。(用快捷键吧 alt + enter
  4. 抽象类实现接口,可以不用实现接口的方法。
  5. 一个类可以同时实现多个接口。class Name implements In1,In2{...}
  6. 接口中的属性只能是 final 的,并且是 public static final 修饰符。修饰符就算不写,还是这样。
  7. 接口中属性的访问形式:接口名.属性名
  8. 接口不能 继承 其他的类,但可以 继承 多个别的接口。(不是也不能 实现 别的接口)
  9. 接口的修饰符只能是 public 和 默认。这点和类的修饰符相同。

9.7.2 实现接口 vs 继承类

  1. 当子类继承父类,就自动拥有父类的所有功能。如果需要扩展功能,可以通过接口方式扩展。
  2. 可以认为,接口 是对于 Java 单继承机制的补充。
  3. 继承的价值主要在于:解决代码的复用性和可维护性。
  4. 接口的价值主要在于:设计。设计好各种规范,让其他类去实现这些方法。
  5. 接口比继承更加灵活。继承需要满足 is - a 的关系,而接口只需要满足 like - a 关系。
  6. 接口在一定程度上实现代码解耦。(即:接口规范性 + 动态绑定机制)

9.7.3 接口的多态特性

  1. 多态参数(接口的引用可以指向实现了接口的类的对象)

    viod work(Inerface01 i1){...} 参数可以传入任意实现该接口的类

  2. 多态数组

  3. 接口存在多态传递现象

9.8 内部类

一个类的内部又完整的嵌套了另一个类结构。被嵌套的类被称为 内部类。

class Outer{		//外部类
	class Inner{	//内部类	
	}
}
class Other{		//外部其他类
}

内部类的最大特点是可以直接访问私有属性,并且可以体现类与类之间的包含关系。

9.8.1 四种内部类

分别是:

  • 定义在外部类的局部位置上
    • 局部内部类:有 类名
    • 匿名内部类:无 类名
  • 定义在外部类的成员位置上
    • 成员内部类:无 static 修饰
    • 静态内部类: static 修饰的类

9.8.2 局部内部类

局部内部类:定义在外部类的局部位置上,并且有类名。(局部位置?比如:方法/代码块里)

class Outer {				//外部类
    public void tools01() {
        class Inner {		//局部内部类
        }
    }
}

#9.8.2.1 使用细节

  1. 定义在外部类的局部位置上,并且有类名。

  2. 可以访问外部类的所有成员,包含私有成员

  3. 局部内部类可以 直接访问 外部类的成员。

  4. 不能添加 访问修饰符,因为其地位相当于局部变量。但,可以使用 final,因为局部变量也能用 final

  5. 作用域 仅仅在定义它的方法或代码块中

  6. 外部类 在方法中,可以创建 局部内部类 的对象实例,然后调用方法。

  7. 外部其他类 不能访问 局部内部类

  8. 如果外部类和局部内部类的成员重名时,默认遵循就近原则。那个场合,访问外部类成员使用 外部类名.this.变量名

    外部类名.this 本质就是 外部类的对象。即,调用了该方法(上例的 tools01 )的对象

    还不懂的话,看一下 这个视频 悟一悟

9.8.3 匿名内部类

匿名内部类:定义在外部类的局部位置,且没有类名

>new 类/接口 (参数列表) {
   类体
>}

匿名内部类本质是没有名字的类,而且是内部类。同时,还是一个对象。

可以用匿名内部类简化开发

一个例子

>class Outer {							//外部类
   public void tools01() {
       Inter whatEver = new Inter(){	//匿名内部类            
       };						
   }
>}
>interface Inter{
>}

其实,这个匿名内部类 new Inter(){} 的运行类型就是 class XXXX implements Inter。系统自动分配的名字是 Outer$1whatEver.getClass = "Outer$1"

JDK 在创建匿名内部类 Outer$1 时,立即创建了一个对象实例,并将地址返回给了 whatEver

匿名内部类使用一次后就不能再次使用(Outer$1 就这一个了)

#9.8.3.1 使用细节

  1. 匿名内部类语法比较独特。其既是一个类的定义,也是一个对象。因此,从语法上看,其既有 定义类的特征,也有 创建对象的特征。
  2. 可以访问外部类的所有成员,包括私有的。
  3. 局部内部类可以 直接访问 外部类的成员。
  4. 不能添加 访问修饰符,因为其地位相当于局部变量。但,可以使用 final,因为局部变量也能用 final
  5. 作用域:仅仅在定义它的方法或方法快中
  6. 外部其他类 不能访问 匿名内部类
  7. 如果外部类和匿名内部类的成员重名时,默认遵循就近原则。那个场合,访问外部类成员使用 外部类名.this.变量名

#9.8.3.2 使用场景

  1. 当作实参直接传递,简洁高效

    public class Homework1 {
        public static void main(String[] args) {		
            new Cellphone().clock(new Bell() {				//看这里看这里
                @Override
                public void belling() {
                    System.out.println("小懒猪起床了!");
                }
            });
        }
    }
    
    interface Bell {
        void ringing();
    }
    
    class Cellphone{
        public void clock(Bell bell){
            bell.ringing();
        }
    }

9.8.4 成员内部类

成员内部类:定义在外部类的成员位置,并且没有 static 修饰。

class Outer{
	class Inner{
	}
}

#9.8.4.1 使用细节

  1. 可以直接访问外部类的所有成员,包括私有的
  2. 可以添加任意访问修饰符。因为,成员内部类的地位就是一个成员。
  3. 作用域 和外部类其他成员相同,为整个类体。
  4. 局部内部类可以 直接访问 外部类的成员。
  5. 外部类可以通过创建对象的方式访问成员内部类
  6. 外部其他类访问成员内部类
    • Outer.Inner name = Outer.new Inner(); 下个方法的缩写
    • Outer.Inner name = new Outer().new Inner();
    • 在外部类中编写一个方法,返回一个 Inner 的对象实例(就是对象的 getter)
  7. 如果外部类和匿名内部类的成员重名时,默认遵循就近原则。那个场合,访问外部类成员使用 外部类名.this.变量名

9.8.5 静态内部类

静态内部类:定义在外部类的成员位置,经由 static 修饰。

class Outer{
	static class Inner{
	}
}

#9.8.5.1 使用细节

  1. 可以直接访问外部类的所有 静态 成员,包括私有的。但不能访问非静态成员
  2. 可以添加访问修饰符。因为,静态内部类的地位就是一个成员。
  3. 作用域 和其他成员相同,为整个类体。
  4. 静态内部类可以 直接访问 外部类的成员。
  5. 外部类可以通过创建对象的方式访问静态内部类
  6. 外部其他类访问静态内部类
    • Outer.Inner name = new Outer.Inner(); 即通过类名直接访问
    • 在外部类中编写一个方法,返回一个 Inner 的对象实例
    • 如果外部类和匿名内部类的成员重名时,默认遵循就近原则。那个场合,访问外部类成员使用 外部类名.变量名(怎么不一样了呢?因为静态内部类访问的都是静态成员)

<Java>9 面向对象编程(高级)
https://i-melody.github.io/2021/12/14/Java/入门阶段/9 面向对象编程(高级)/
作者
Melody
发布于
2021年12月14日
许可协议