Skip to content

0373 0423 第十章 面向对象编程(高级部分)

10.1 类变量和类方法

10.1.3 类变量快速入门

定义一个变量 ,是一个类变量 (静态变量) static 静态

该变量最大的特点就是会被 Child 类的所有的对象实例共享


10.1.4 类变量内存布局

  1. 存储位置

    • 类变量存储在 方法区(Method Area)(JDK 1.8 之前)或 元空间(Metaspace)(JDK 1.8 及之后)。方法区/元空间是 JVM 管理的一块共享内存区域,用于存储类的元数据、常量池、静态变量等信息。
    • 与实例变量(存储在堆中的对象实例内)不同,类变量在类加载时(通常是类的初始化阶段)分配内存,且整个程序运行期间只有一份副本,供所有实例和类本身共享。
  2. 内存分配

    • 类变量的内存分配发生在==类加载==的 初始化阶段<clinit> 方法执行时)。JVM 会为每个静态变量分配内存,并根据变量的类型赋予默认值(例如,int 为 0,boolean 为 false,引用类型为 null)。
    • 静态变量的内存大小由其数据类型决定。例如:
      • int:4 字节
      • long:8 字节
      • boolean:1 字节(实际可能因内存对齐而占用更多)
      • 引用类型(如 String):在 64 位 JVM 中,开启指针压缩(默认)时为 4 字节,未开启时为 8 字节。
  3. 内存布局特点

    • 单一副本:类变量在内存中只有一份,存储在类的元数据结构中(Klass 对象)。无论创建多少个类实例,静态变量的内存地址和值都保持一致。
    • 对齐填充:JVM 要求对象的内存布局满足 8 字节对齐(在 HotSpot 中)。如果类变量的总大小不是 8 的倍数,JVM 可能会添加填充字节(padding)以确保对齐。
    • 访问方式:类变量通过类名直接访问(例如 ClassName.staticField),无需创建实例。JVM 通过类的元数据(Klass 指针)定位到方法区/元空间中的静态变量存储区域。
  4. 对象头与类变量的关系

    • 类变量不属于某个对象实例,因此它们不包含在对象的内存布局 (对象头 + 实例数据 + 对齐填充) 中。对象的内存布局仅包括实例变量,而类变量独立存储在方法区/元空间。
    • 类变量的访问不涉及对象头(Mark Word 或 Klass 指针),而是通过类的元数据直接定位。 [[Klass指针和Mark Word]]

10.1.5 什么是类变量

类变量(也称为静态变量)是使用 static 关键字声明的变量,它们属于类而不是类的实例,因此在内存中的存储方式和实例变量有显著区别。


10.1.6 如何定义类变量

定义语法: - 访问修饰 static 数据类型 变量名; - static 访问修饰符 数据类型 变量名;


10.1.7 如何访问类变量

类名.类变量名 对象名.类变量名

静态变量的访问修饰符的访问权限范围和普通属性是一样的


10.1.8 类变量使用注意事项和细节讨论

  1. 什么时候需要用类变量
    • 当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量):
    • 比如:
      • 定义学生类,统计所有学生共交多少钱。
      • Student (name, static fee)
  2. 类变量与实例变量(普通属性)区别
    • 类变量是该类的所有对象共享的,而实例变量是每个对象独享的。
  3. 加上 static(称为类变量或静态变量,否则称为实例变量 / 普通变量 / 非静态变量
  4. 类变量可以通过类名.类变量名或者对象名.类变量名来访问,但 java 设计者推荐我们使用 类名.类变量名 方式访问。(前提是满足访问修饰符的访问权限和范围)
  5. 实例变量不能通过 类名.类变量名 方式访问。
  6. 类变量是在类加载时就初始化,也就是说,即使你没有创建对象,只要类加载了,就可以使用类变量了。
  7. 类变量的生命周期是随类的加载开始,随着类消亡而销毁。

10.1.9 类方法基本介绍

方法也叫静态方法。形式如下:

访问修饰符 static 数据返回类型 方法名(){ }【推荐】 static 访问修饰符 数据返回类型 方法名(){ }


10.1.10 类方法的调用

使用方式:

类名.类方法名 对象名.类方法


10.1.11 类方法应用案例

如果我们希望不创建实例,也可以调用某个方法 (即当做工具来使用) 这时,把方法做成静态方法时非常合适

  1. 当方法使用了 static 修饰后,该方法就是静态方法
  2. 静态方法就可以访问静态属性/变量

10.1.12 类方法经典的使用场景

当方法中不涉及到任何和对象相关的成员,则可以将方法设计成静态方法,提高开发效率。 - 比如: 具类中的方法 utils - Math 类、Array s 类、Collections 集合类

在程序员实际开发,往往会将一些通用的方法,设计成静态方法,这样我们不需要创建对象就可以使用了,比如打印一维数组,冒泡排序, 完成某个计算任务等。


10.1.13 类方法使用注意事项和细节讨论

  1. 类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法:
    • 类方法中无 this 的参数
    • 普通方法中含着 this 的参数
  2. 类方法可以通过类名调用,也可以通过对象名调用。
  3. 普通方法和对象有关,需要通过对象名调用,比如 对象名.方法名(叁数), 不能通过类名调用。
  4. 类方法中不允许使用和对象有关的关键字,比如 this 和 n super 普通方法(成员方法)可以
  5. 类方法(静态方法)中只能访问静态变量或静态方法。
  6. 普通成员方法,既可以访问非静态成员,也可以访问静态成员。

静态方法,只能访问静态的成员,非静态的方法,可以访问静态成员和非静态成员(必须遵守访问权限)


10.2 理解 main 方法语法

10.2.1 深入理解 main 方法

解释 main 方法的形式:public static main(String[] args){}

  1. main 方法时虚拟机调用
  2. java 虚拟机需要调用类的 main。方法,所以该方法的访问权限必须是 public
  3. java 虚拟机在执行 maino 方法时不必创建对象,所以该方法必须是 static
  4. 该方法接收 String 类型的数组参数,该数组中保存执行 java 命令时传递给所运行的类的参数
    • javac a.java
    • java a.class tom jack
  5. java 执行的程序 参数1 参数2 参数 3

10.2.2 特别提示

  1. 在 main () 方法中,我们可以直接调用 main 方法所在类的静态方法或静态属性。
  2. 但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员

10.3 代码块

10.3.1 基本介绍

  • 代码化块又称为初始化块,属于类中的成员[即是类的一部分], 类似于方法,将逻辑语句封装在方法体中,通过{}包围起来。
  • 但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或创建对象时隐式调用

10.3.2 基本语法

[修饰符]{ 代码 ’}`

说明注意: 1. 修饰符可选,要写的话,也只能写 static 2. 代码块分为两类, - 使用 static 修饰的,叫 静态代码块 - 没有 static 修饰的,叫 普通代码块/非静态代码块。 3. 逻辑语句可以为任何逻辑语句(输入、输出、方法凋用、循环、判断等) 4. ; 号可以写上,也可以省略。


10.3.3 代码块的好处和案例演示

  1. 相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化的操
  2. 场景: 如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的重用
  3. 这样当我们不管调用哪个构造器,创建对象,都会先调用代码块的内容
  4. 代码块调用的顺序优先于构造器

10.3.4 代码块使用注意事项和细节讨论

1. 注意事项

  1. static 代码块也叫静态代码块,作用就是对类进行初始化,而旦它随着类的加载而执行,并且只会执行一次。如果是普通代码块,每创建一个对象,就执行。
  2. 类什么时候被加载【重要背讲]
    • 创建对象实例时(new
    • 创建子类对象实例,父类也会被加载
    • 使用类的静态成员时(静态属性,静态方法)
  3. 普通的代码块,在创建对象实例时,会被隐式的调用。 被创建一次,就 会调用一次。 如果只是使用类的静态成员时,普通代码块并不会执行

2. 小结

  1. static代码块是类加载时,执行,只会执行一次
  2. 普通代码块是在创建对象时调用的,创建一次,调用一次
  3. 类加载的种情况,需要记住.

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

  1. 调用静态代码块和静态属性初始化 注 意:静态代码块和静态属性初始化调用的优先级一样, 如果有多个静态代码块和多个静态 变量初始化,则按他们定义的顺序调用
  2. 用普通代码块和普通属性的初始化 注意:普通代码块和普通属性初始化调用的优先级一样, 如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用
  3. 调用构造方法。

4. 补充细节

  1. 构造器的最前面其实隐含了 super。和调用普通代码块
  2. 静态相关的代码块,属性初始化,在类加载时,就执行完毕,因此是优先于构造器和普通代码块执行
  3. 静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员

5. 代码块,变量,构造器初始化详细流程

  1. 父类的静态代码块和静态变量初始化(按代码中定义的顺序执行)。
  2. 子类的静态代码块和静态变量初始化(按代码中定义的顺序执行)。
  3. 父类的实例变量初始化和实例代码块(按代码中定义的顺序执行)。
  4. 父类的构造器
  5. 子类的实例变量初始化和实例代码块(按代码中定义的顺序执行)。
  6. 子类的构造器

10.4 单例设计模式

10.4.1 什么是设计模式

  1. 静态方法和属性的经典使用
  2. 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式

10.4.2 什么是单例模式

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

单例模式有两种方式: - 饿汉式 - 懒汉式


10.4.3 单例模式应用实例

  1. 构造器私有化 -> 防止直接new
  2. 类的内部创建对象
  3. 向外暴露一个静态的公共方法。getlnstance

- 为了能够在静态方法中,返回 gf 对象,需要将其修饰为 static

10.4.4 饿汉式 VS 懒汉式1

  1. 二者最主要的区别在于创建对象的时机不同: 饿汉式是在类加载就创建了对象实例懒汉式是在使用时才创建

  2. 饿汉式不存在线程安全问题,懒汉式存在线程安全问题

  3. 饿汉式存在浪费资源的可能。因为如果程序员一个对象实例都没有使用,那么饿汉式创建的对象就浪费了, 懒汉式是使用时才创建,就不存在这个问题。

  4. 在我们javaSE标准类中,java.Iang.Runtime 就是典的单例模式

  5. static final 变量必须在类加载的静态初始化阶段(早于对象创建)完成赋值


10.5 final 关键字

10.5.1 基本介绍

final 中文意思:最后的,最终的.

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

在某些情况下,程序员可能有以下需求,就会使用到 final:

  1. 当不希望类被继承时,可以用final修饰.
  2. 当不希望父类的某个方法被子类覆盖 / 重写(override)时用以用 final 关键字修饰。 访问修饰符 final 返回类型 方法名
  3. 当不希望类的的某个属性的值被修改,可以用 final 修饰. public final double TAX RATE=0.08
  4. 当不希望某介局部变量被修改,可以使用final修饰 final double TAX RATE = 0.08

10.5.2 final 使用注意事项和细节讨论

  1. final修饰的属性又叫常量,用 XX XX XX 来命名

  2. final修饰的属性在定义时,必须赋初值,并直以后不能再修改,赋值可以在如下位置之一

    1. 定义时 如 public final double TAX RATE=0.08;
    2. 在构造器中
    3. 在代码块中。
  3. 如果final修饰的属性是静态的,则初始化的位置只能是定义时静态代码块不能在构造器中赋值。

  4. final类不能继承,但是可以实例化对象。

  5. 如果类不是 final 类,但是含有 final 方法,则该方法虽然不能重写,但是可以被继承,仍然遵守继承的机制

  6. 一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成 final 方法。

  7. final 不能修饰构造方法(即构造器)

  8. finalstatic 往往搭配使用,效率更高,不会导致类加载.底层编译器做了优化处理。[[final static 变量与 final static 方法#为什么不会类加载|不会类加载的原因]] [[变量和常量#一、常量的分类|常量分类]]

    • 可以将 final static 理解为更安全、更现代的 #define 替代品,但仅限于常量定义场景。
  9. 包装类(lnteger,Double,Float, Boolean等都是final),String也是final


10.6 抽象类

10.6.1 先看一个问题

父类方法不确定性的问题 1. 考虑将该方法设计为抽象(abstract)方法 2. 所谓抽象方法就是没有实现的方法 3. 所谓没有实现就是指,没有方法体 4. 当一个类中存在抽象方法时,需要将该类声明为 abstract 类 5. 一般来说,抽象类会被继承,有其子类来实现抽象方法.

public class Abstract01 {

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

abstract class Animal { 
    private String name;

    public Animal(String name) { 
        this.name = name;
    }

    public abstract void eat() ;

}

10.6.2 解决之道-抽象类快速入门

当父类的一些方法不能确定时,可以用 abstract 关键字来修饰该方法,这个方法就是抽象方法,用 abstract 来修饰该类就是抽象类。

我们看看如何把 Animal做成抽象类,并 让子类Cat类 实现。

abstract class Animal{ 
    String name;
    int age; 
    abstract public void cry();
}


10.6.3 抽象类的介绍

  1. abstract 关犍字来修饰一个类时,这个类就叫抽象类

    • 访问修饰符abstract 类名{}
  2. 用 abstract 关健字来修饰一个方法时,这个方法就是抽象方法访问修饰符 abstract 返回类型 方法名(参数列表); 没有方法体

  3. 抽象类的价值更多作用是在于设计,是设计者设计好后,让子类继承并实现

4. 抽象类,是考官比较爱问的知识点,在框架和设计模式使用较多

10.6.4 抽象类使用的注意事项和细节讨论

  1. 抽象类不能被实例化

  2. 抽象类不一定要包含 abstract 方法。也就是说,抽象类可以没有 abstract 方法

  3. 一旦类包含了 abstract 方法, 则这个类必须声明为 abstract

  4. abstract 只能修饰类和方法,不能修饰属性和其它的。

  5. 抽象类可以有任意成员【抽象类本质还是类】, 比如:非抽象方法、构造器、静态属性等等

  6. 抽象方法不能有主体,即不能实现. abstracet void aaa(){}

  7. 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类。

  8. 8)抽象方法不能使用 private、final 和 static 来修饰,因为这些关键字都是和重写相违背的


10.7 抽象类最佳实践-模板设计模式

10.7.1 基本介绍

抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子 类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。


10.7.2 模板设计模式能解决的问题

  1. 当功能内部一部分实现是确定,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。

  2. 编写一个抽象父类,父类提供了多个子类的通用方法,并把一个或多个方法留给其子类实现,就是一种模板模式.


10.8 接口

10.8.2 接口快速入门

这样的设计需求在 java / php / .net / go 中也是会大量存在的, 我曾经说过,一个程序就是一个世界,在现实世界存在的情况,在程序中也会出现。

public interface UsbInterface { //接口
    //规定接口的相关方法,老师规定的.即规范...
    public void start();
    public void stop();
}

public class Camera implements UsbInterface { //实现接口,就是把接口方法实现
    @Override
    public void start() {
        System.out.println("相机开始工作...");
    }
    @Override
    public void stop() {
        System.out.println("相机停止工作....");
    }
}

// Phone 类 实现 UsbInterface
// 解读 1. 即 Phone 类需要实现 UsbInterface 接口 规定/声明的方法
public class Phone implements UsbInterface {
    @Override
    public void start() {
        System.out.println("手机开始工作...");
    }
    @Override
    public void stop() {
        System.out.println("手机停止工作.....");
    }
}


public class Interface01 {
    public static void main(String[] args) {
    //创建手机,相机对象
    //Camera 实现了 UsbInterface
        Camera camera = new Camera();
    //Phone 实现了 UsbInterface
        Phone phone = new Phone();
    //创建计算机
        Computer computer = new Computer();
        computer.work(phone);//把手机接入到计算机
        System.out.println("===============");
        computer.work(camera);//把相机接入到计算机
    }
}

10.8.3 基本介绍

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

interface 接口名{
    // 属性
    // 抽象方法
}

class 类名 implements 接口{
    // 自己属性;
    // 自己方法;必须实现的接口的抽象方法
} 

小结:

  • 接口是更加抽象的抽象的类,抽象类里的方法可以有方法体,接口里的所有方法都没有方法体(jdk7.0)。

  • 接口体现了程序设计的多态和高内聚低偶合的设计思想。

  • 特别说明:Jdk8.0后接口类可以有静态方法,默认方法,也就是说接口中可以有方法的具体实现

    • 抽象方法 可以不加修饰符,默认 abstractvoid aaa()
    • 默认方法 需要加上 default 关键字修饰 :default void aaa()
    • 静态方法 需要加上 static 关键字修饰: puulic static void aaa()

10.8.4 深入讨论

public interface DBInterface { //项目经理
    public void connect();//连接方法
    public void close();//关闭连接
}
//A 程序员连接 Mysql
public class MysqlDB implements DBInterface {
    @Override
    public void connect() {
        System.out.println("连接 mysql");
    }
    @Override
    public void close() {
        System.out.println("关闭 mysql");
    }
}//B 程序员连接 Oracle
public class OracleDB implements DBInterface {
    @Override
    public void connect() {
        System.out.println("连接 oracle");
    }
    @Override
    public void close() {
        System.out.println("关闭 oracle");
    }
}

public class Interface03 {
    public static void main(String[] args) {
        MysqlDB mysqlDB = new MysqlDB();
        t(mysqlDB);
        OracleDB oracleDB = new OracleDB();
        t(oracleDB);
    }
    public static void t(DBInterface db) {
        db.connect();
        db.close();
    }
}

10.8.5 注意事项和细节

  1. 接口不能被实例化

  2. 接口中所有的方法不写就是 public 方法,接口中抽象方法,可以不用 abstract 修饰 void aaa(); 实际上是 abstract void aa();

  3. void aaa(){}

  4. 一个普通类实现接口,就必须将该接口的所有方法都实现

  5. 抽象类实现接口,可以不用实现接口的方法

  6. 一个类同时可以实现多介接口

  7. 接口中的属性只能是 final 而且是 public static final 修饰符。 比如: int a = 1; 实际上是 public static final int a = 1;必须初始化

  8. 接口中属性的访问形式: 接口名.属性名

  9. 接口不能继承其它的类,但是可以继承多个别的接口 interface A extends B,C {}

  10. 口的修饰符只能是 public默认,这点和类的修饰符是一样的


10.8.7 实现接口 vs 继承类

小结: - 当子类继承了父类,就自动的拥有父类的功能 - 如果子类需要扩展功能,可以通过实现接口的方式扩展. - 可以理解实现接口是对 java 单继承机制的一种补充.

  • 接口和继承解决的问题不同

    • 继承的价值主要在于:解决代码的==复用性和可维护性==。
    • 接口的价值主要在于:设计,设计好各种规范(方法),让其它类去实现这些方法。即更加的灵活.
  • 接口比继承更加灵活

    • 接口比继承更加灵活,继承是满足is-a的 关系,而 接口只需满足like-a的关系。
    • 接口在一定程度上实现代码解耦 ( 即:接 口规范性+动态绑定机制 )

10.8.8 接口的多态特性

  1. 多态参数

    • 在前面的Usb接口案例,Usblnterface usb f 既可以接收手机对象,又可以接收相机对象,就体现了接口多态
    • 接口引用可以指向实现了接口的类的对象
  2. 多态数组

    • 给Usb数组中,存放 Phonecamera 对象,Phone 类还有一个特有的方法 call (),请遍历 Usb 数组,如果是 Phone 对象,除了调用 Usb 接口定义的方法外,还需要调用 Phone 特有方法 call().
  3. 接口多态传递现象

    • 如果 IG 继承了 IH 接口,而 Teacher 类实现了 IG 接口
    • 那么,实际上就相当于 Teacher 类也实现了 IH 接口.
    • 这就是所谓的接口多态传递现象

10.9 内部类

如果定义类在局部位置(方法中/代码块) : 1. 局部内部类 2. 匿名内部类

定义在成员位置 : 1. 成员内部类 2. 静态内部类


10.9.1 基本介绍

一个类的内部又完整的嵌套了另一个类结构。 被嵌套的类称为内部类(inner class), 嵌套其他类的类称为外部类( outer class)。

是我们类的第五大成员

类的五大成员

属性、方法、构造器、代码块、内部类

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


10.9.2 基本语法

claas Outer{ 外部类 class Inner{ 内部类 } }

class Other{} // 外部其他类


10.9.3 快速入门案例

public class InnerClass01 { //外部其他类
    public static void main(String[] args) {
    }
}
class Outer { //外部类

    private int n1 = 100;//属性

    public Outer(int n1) {//构造器
        this.n1 = n1;
    }

    public void m1() {//方法
        System.out.println("m1()");
    }

    {
        //代码块
        System.out.println("代码块...");
    }

    class Inner { //内部类, 在 Outer 类的内部
    }
}

10.9.4 内部类的分类

  • 定义在外部类局部位置上

    • (比如方法内):
    • 局部内部类(有类名)
    • 匿名内部类(没有类名,重点讲讲讲讲讲讲讲讲)
  • 定义在外部类的成员位置上 :

    1. 成员内部类(没用static修饰)
    2. 静态内部类(使用static修饰)

10.9.5 局部内部类的使用

说明:局部内部类是定义在外部类的局部位置,比如方法中,并且有类名。

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

  2. 不能添加访问修饰符(可以用 final 修饰)

    • 因为它的地位就是一个局部变量。
    • 局部变量是不能使用修饰符的。
    • 但是可以使用final 修饰,因为局部变量也可以使用final
  3. 作用域:仅仅在定义它的方法或代码块中

  4. 局部内部类 --- 访问 --> 外部类的成员

    • 访问方式:直接访闻外部类
  5. 外部类 --- 访问 --> 局部内部类的成员

    • 访问方式: 创建对象,再访问(注意:必须在作用域内)
  6. 外部其他类 --- 不能访问 --> 局部内部类

    • (因为局部内部类地位是一个局部变量)
  7. 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问

    • System.out.println(外部类的 n2 = " +外部类名 .this.n2);

局部内部类定义在方法中/ 代码块 作用域在方法体或者代码块中 本质仍然是一个类


10.9.6 匿名内部类的使用

简要介绍

  1. 本质是类
  2. 内部类
  3. 该类没有名字
  4. 同时还是一个对象

说明:匿名内部类是定义在外部类的局部位置,比如方法中,并且没有类名

具体细节和使用

  1. 匿名内部类的基本语法

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

  2. 匿名内部类的语法比较奇特,请大家注意,因为匿名内部类既是一个类的定义,同时它本身也是一个对象,因此从语法上看,它脩福定义类的 特征,也有创建对象的特征,对前面代码分析可以看出这个特点,因此可以调用匿名内部类方法。

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

  4. 不能添加访问修饰符,因为它的地位就是一介局部变量。

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

  6. 匿名内部类 --- 访问 --> 外部类成员(访问方式:直接访问)

  7. 外部其他类 --- 不能访问 --> 匿名内部类(因为匿名内部类地位是一个局部变量)

  8. 如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则,

  9. 如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问


10.9.9 成员内部类的使用

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

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

  2. 可以添加任意访问修饰符(public、protected 、默认、private),因为它的地位就是一个成员。

  3. 作用域: 和外部类的其他成员一样,为整个类体 比如前面案例,在外部类的成员方法中创建成员内部类对象,再调用方法.

  4. 成员内部类 --- 访问 --> 外部类成员(比如:属性) 访问方式:直接访问

  5. 外部类 --- 访问 --> 成员内部类 访问方式:创建对象,再访问

  6. 外部其他类 --- 访问 --> 成员内部类

  7. 如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问

outer08.new Inner08();相当于把 new Inner08()当做是 outer08 成员


10.9.10 静态内部类的使用

![[内部类#六、内部类的对比]]


10.10卖油翁和老黄牛




  1. 为什么构造器中不能初始化 static final 变量?

    • 时间点不匹配:构造器在对象实例化时执行,而 static final 变量必须在类加载的静态初始化阶段(早于对象创建)完成赋值。
    • 语法限制final 变量只能赋值一次,构造器的多次调用(如果创建多个对象)会导致尝试多次赋值,违反 final 规则。
    • 编译器检查:Java 编译器会强制要求 static final 变量在声明或静态代码块中初始化,构造器中的赋值尝试会导致编译错误,如“cannot assign a value to final variable”。

    总结

    • 饿汉式static final 变量适合在声明时或静态代码块中初始化,构造器中不能初始化(语法不允许)。
    • 懒汉式:直接使用 static final 会导致无法延迟加载,构造器中也不能初始化。可以通过静态内部类结合 static final 实现延迟加载。
    • 原因static final 变量的初始化必须在类加载时完成,而构造器是实例级别的,时间点和作用域不匹配。