0373 0423 第十章 面向对象编程(高级部分)
10.1 类变量和类方法¶
10.1.3 类变量快速入门¶
定义一个变量 ,是一个类变量 (静态变量) static 静态
该变量最大的特点就是会被 Child 类的所有的对象实例共享
10.1.4 类变量内存布局¶
-
存储位置:
- 类变量存储在 方法区(Method Area)(JDK 1.8 之前)或 元空间(Metaspace)(JDK 1.8 及之后)。方法区/元空间是 JVM 管理的一块共享内存区域,用于存储类的元数据、常量池、静态变量等信息。
- 与实例变量(存储在堆中的对象实例内)不同,类变量在类加载时(通常是类的初始化阶段)分配内存,且整个程序运行期间只有一份副本,供所有实例和类本身共享。
-
内存分配:
- 类变量的内存分配发生在==类加载==的 初始化阶段(
<clinit>方法执行时)。JVM 会为每个静态变量分配内存,并根据变量的类型赋予默认值(例如,int为 0,boolean为false,引用类型为null)。 - 静态变量的内存大小由其数据类型决定。例如:
int:4 字节long:8 字节boolean:1 字节(实际可能因内存对齐而占用更多)- 引用类型(如
String):在 64 位 JVM 中,开启指针压缩(默认)时为 4 字节,未开启时为 8 字节。
- 类变量的内存分配发生在==类加载==的 初始化阶段(
-
内存布局特点:
- 单一副本:类变量在内存中只有一份,存储在类的元数据结构中(Klass 对象)。无论创建多少个类实例,静态变量的内存地址和值都保持一致。
- 对齐填充:JVM 要求对象的内存布局满足 8 字节对齐(在 HotSpot 中)。如果类变量的总大小不是 8 的倍数,JVM 可能会添加填充字节(padding)以确保对齐。
- 访问方式:类变量通过类名直接访问(例如
ClassName.staticField),无需创建实例。JVM 通过类的元数据(Klass 指针)定位到方法区/元空间中的静态变量存储区域。
-
对象头与类变量的关系:
- 类变量不属于某个对象实例,因此它们不包含在对象的内存布局
(对象头 + 实例数据 + 对齐填充)中。对象的内存布局仅包括实例变量,而类变量独立存储在方法区/元空间。 - 类变量的访问不涉及对象头(Mark Word 或 Klass 指针),而是通过类的元数据直接定位。 [[Klass指针和Mark Word]]
- 类变量不属于某个对象实例,因此它们不包含在对象的内存布局
10.1.5 什么是类变量¶
类变量(也称为静态变量)是使用
static关键字声明的变量,它们属于类而不是类的实例,因此在内存中的存储方式和实例变量有显著区别。
10.1.6 如何定义类变量¶
定义语法: -
访问修饰 static 数据类型 变量名;-static 访问修饰符 数据类型 变量名;
10.1.7 如何访问类变量¶
类名.类变量名对象名.类变量名
静态变量的访问修饰符的访问权限范围和普通属性是一样的
10.1.8 类变量使用注意事项和细节讨论¶
- 什么时候需要用类变量
- 当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量):
- 比如:
- 定义学生类,统计所有学生共交多少钱。
Student (name, static fee)
- 类变量与实例变量(普通属性)区别
- 类变量是该类的所有对象共享的,而实例变量是每个对象独享的。
- 加上
static(称为类变量或静态变量,否则称为实例变量 / 普通变量 / 非静态变量 - 类变量可以通过类名.类变量名或者对象名.类变量名来访问,但 java 设计者推荐我们使用
类名.类变量名方式访问。(前提是满足访问修饰符的访问权限和范围) - 实例变量不能通过
类名.类变量名方式访问。 - 类变量是在类加载时就初始化,也就是说,即使你没有创建对象,只要类加载了,就可以使用类变量了。
- 类变量的生命周期是随类的加载开始,随着类消亡而销毁。
10.1.9 类方法基本介绍¶
方法也叫静态方法。形式如下:
访问修饰符 static 数据返回类型 方法名(){ }【推荐】static 访问修饰符 数据返回类型 方法名(){ }
10.1.10 类方法的调用¶
使用方式:
类名.类方法名对象名.类方法
10.1.11 类方法应用案例¶
如果我们希望不创建实例,也可以调用某个方法 (即当做工具来使用) 这时,把方法做成静态方法时非常合适
- 当方法使用了 static 修饰后,该方法就是静态方法
- 静态方法就可以访问静态属性/变量
10.1.12 类方法经典的使用场景¶
当方法中不涉及到任何和对象相关的成员,则可以将方法设计成静态方法,提高开发效率。
- 比如: 具类中的方法 utils
- Math 类、Array s 类、Collections 集合类
在程序员实际开发,往往会将一些通用的方法,设计成静态方法,这样我们不需要创建对象就可以使用了,比如打印一维数组,冒泡排序, 完成某个计算任务等。
10.1.13 类方法使用注意事项和细节讨论¶
- 类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法:
- 类方法中无
this的参数 - 普通方法中含着
this的参数
- 类方法中无
- 类方法可以通过类名调用,也可以通过对象名调用。
- 普通方法和对象有关,需要通过对象名调用,比如
对象名.方法名(叁数), 不能通过类名调用。 - 类方法中不允许使用和对象有关的关键字,比如
this和 nsuper。 普通方法(成员方法)可以 - 类方法(静态方法)中只能访问静态变量或静态方法。
- 普通成员方法,既可以访问非静态成员,也可以访问静态成员。
静态方法,只能访问静态的成员,非静态的方法,可以访问静态成员和非静态成员(必须遵守访问权限)
10.2 理解 main 方法语法¶
10.2.1 深入理解 main 方法¶
解释 main 方法的形式:public static main(String[] args){}
- main 方法时虚拟机调用
- java 虚拟机需要调用类的 main。方法,所以该方法的访问权限必须是 public
- java 虚拟机在执行 maino 方法时不必创建对象,所以该方法必须是 static
- 该方法接收
String类型的数组参数,该数组中保存执行 java 命令时传递给所运行的类的参数javac a.javajava a.class tom jack
java 执行的程序 参数1 参数2 参数 3
10.2.2 特别提示¶
- 在 main () 方法中,我们可以直接调用 main 方法所在类的静态方法或静态属性。
- 但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员
10.3 代码块¶
10.3.1 基本介绍¶
- 代码化块又称为初始化块,属于类中的成员[即是类的一部分], 类似于方法,将逻辑语句封装在方法体中,通过{}包围起来。
- 但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或创建对象时隐式调用。
10.3.2 基本语法¶
[修饰符]{代码 ’}`
说明注意:
1. 修饰符可选,要写的话,也只能写 static
2. 代码块分为两类,
- 使用 static 修饰的,叫 静态代码块
- 没有 static 修饰的,叫 普通代码块/非静态代码块。
3. 逻辑语句可以为任何逻辑语句(输入、输出、方法凋用、循环、判断等)
4. ; 号可以写上,也可以省略。
10.3.3 代码块的好处和案例演示¶
- 相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化的操
- 场景: 如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的重用
- 这样当我们不管调用哪个构造器,创建对象,都会先调用代码块的内容
- 代码块调用的顺序优先于构造器
10.3.4 代码块使用注意事项和细节讨论¶
1. 注意事项¶
- static 代码块也叫静态代码块,作用就是对类进行初始化,而旦它随着类的加载而执行,并且只会执行一次。如果是普通代码块,每创建一个对象,就执行。
- 类什么时候被加载【重要背讲]
- 创建对象实例时(
new) - 创建子类对象实例,父类也会被加载
- 使用类的静态成员时(静态属性,静态方法)
- 创建对象实例时(
- 普通的代码块,在创建对象实例时,会被隐式的调用。 被创建一次,就 会调用一次。 如果只是使用类的静态成员时,普通代码块并不会执行
2. 小结¶
static代码块是类加载时,执行,只会执行一次- 普通代码块是在创建对象时调用的,创建一次,调用一次
- 类加载的种情况,需要记住.
3. 创建一个对象时,在一个类调用顺序是:¶
- 调用静态代码块和静态属性初始化 注 意:静态代码块和静态属性初始化调用的优先级一样, 如果有多个静态代码块和多个静态 变量初始化,则按他们定义的顺序调用
- 用普通代码块和普通属性的初始化 注意:普通代码块和普通属性初始化调用的优先级一样, 如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用
- 调用构造方法。
4. 补充细节¶
- 构造器的最前面其实隐含了
super。和调用普通代码块 - 静态相关的代码块,属性初始化,在类加载时,就执行完毕,因此是优先于构造器和普通代码块执行
- 静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员
5. 代码块,变量,构造器初始化详细流程¶
- 父类的静态代码块和静态变量初始化(按代码中定义的顺序执行)。
- 子类的静态代码块和静态变量初始化(按代码中定义的顺序执行)。
- 父类的实例变量初始化和实例代码块(按代码中定义的顺序执行)。
- 父类的构造器。
- 子类的实例变量初始化和实例代码块(按代码中定义的顺序执行)。
- 子类的构造器。
10.4 单例设计模式¶
10.4.1 什么是设计模式¶
- 静态方法和属性的经典使用
- 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式
10.4.2 什么是单例模式¶
单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法
单例模式有两种方式: - 饿汉式 - 懒汉式
10.4.3 单例模式应用实例¶
- 构造器私有化 -> 防止直接
new - 类的内部创建对象
- 向外暴露一个静态的公共方法。
getlnstance
- 为了能够在静态方法中,返回 gf 对象,需要将其修饰为 static¶
10.4.4 饿汉式 VS 懒汉式1¶
-
二者最主要的区别在于创建对象的时机不同: 饿汉式是在类加载就创建了对象实例 而懒汉式是在使用时才创建。
-
饿汉式不存在线程安全问题,懒汉式存在线程安全问题。
-
饿汉式存在浪费资源的可能。因为如果程序员一个对象实例都没有使用,那么饿汉式创建的对象就浪费了, 懒汉式是使用时才创建,就不存在这个问题。
-
在我们javaSE标准类中,
java.Iang.Runtime就是典的单例模式 -
static final变量必须在类加载的静态初始化阶段(早于对象创建)完成赋值
10.5 final 关键字¶
10.5.1 基本介绍¶
final中文意思:最后的,最终的.
final 可以修饰类、属性、方法和局部变量
在某些情况下,程序员可能有以下需求,就会使用到 final:
- 当不希望类被继承时,可以用final修饰.
- 当不希望父类的某个方法被子类覆盖 / 重写(
override)时用以用final关键字修饰。访问修饰符 final 返回类型 方法名 - 当不希望类的的某个属性的值被修改,可以用
final修饰.public final double TAX RATE=0.08 - 当不希望某介局部变量被修改,可以使用final修饰
final double TAX RATE = 0.08
10.5.2 final 使用注意事项和细节讨论¶
-
final修饰的属性又叫常量,用
XX XX XX来命名 -
final修饰的属性在定义时,必须赋初值,并直以后不能再修改,赋值可以在如下位置之一
- 定义时
如
public final double TAX RATE=0.08; - 在构造器中
- 在代码块中。
- 定义时
如
-
如果final修饰的属性是静态的,则初始化的位置只能是定义时静态代码块不能在构造器中赋值。
-
final类不能继承,但是可以实例化对象。 -
如果类不是
final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承,仍然遵守继承的机制 -
一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成
final方法。 -
final不能修饰构造方法(即构造器) -
final和static往往搭配使用,效率更高,不会导致类加载.底层编译器做了优化处理。[[final static 变量与 final static 方法#为什么不会类加载|不会类加载的原因]] [[变量和常量#一、常量的分类|常量分类]]- 可以将
final static理解为更安全、更现代的#define替代品,但仅限于常量定义场景。
- 可以将
-
包装类(
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类 实现。
10.6.3 抽象类的介绍¶
-
用
abstract关犍字来修饰一个类时,这个类就叫抽象类访问修饰符abstract 类名{}
-
用 abstract 关健字来修饰一个方法时,这个方法就是抽象方法访问修饰符
abstract 返回类型 方法名(参数列表);没有方法体 -
抽象类的价值更多作用是在于设计,是设计者设计好后,让子类继承并实现
4. 抽象类,是考官比较爱问的知识点,在框架和设计模式使用较多¶
10.6.4 抽象类使用的注意事项和细节讨论¶
-
抽象类不能被实例化
-
抽象类不一定要包含
abstract方法。也就是说,抽象类可以没有abstract方法 -
一旦类包含了
abstract方法, 则这个类必须声明为abstract -
abstract只能修饰类和方法,不能修饰属性和其它的。 -
抽象类可以有任意成员【抽象类本质还是类】, 比如:非抽象方法、构造器、静态属性等等
-
抽象方法不能有主体,即不能实现.
abstracet void aaa(){}❌ -
如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类。
-
8)抽象方法不能使用
private、final 和 static来修饰,因为这些关键字都是和重写相违背的
10.7 抽象类最佳实践-模板设计模式¶
10.7.1 基本介绍¶
抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子 类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。
10.7.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 基本介绍¶
接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,在根据具体情况把这些方法写出来。语法:
小结:
-
接口是更加抽象的抽象的类,抽象类里的方法可以有方法体,接口里的所有方法都没有方法体(jdk7.0)。
-
接口体现了程序设计的多态和高内聚低偶合的设计思想。
-
特别说明:Jdk8.0后接口类可以有静态方法,默认方法,也就是说接口中可以有方法的具体实现
- 抽象方法 可以不加修饰符,默认
abstract:void 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 注意事项和细节¶
-
接口不能被实例化
-
接口中所有的方法不写就是
public方法,接口中抽象方法,可以不用abstract修饰void aaa();实际上是abstract void aa(); -
void aaa(){}❌ -
一个普通类实现接口,就必须将该接口的所有方法都实现。
-
抽象类实现接口,可以不用实现接口的方法
-
一个类同时可以实现多介接口
-
接口中的属性只能是
final而且是public static final修饰符。 比如:int a = 1;实际上是public static final int a = 1;(必须初始化) -
接口中属性的访问形式:
接口名.属性名 -
接口不能继承其它的类,但是可以继承多个别的接口
interface A extends B,C {} -
接口的修饰符只能是
public和默认,这点和类的修饰符是一样的
10.8.7 实现接口 vs 继承类¶
小结: - 当子类继承了父类,就自动的拥有父类的功能 - 如果子类需要扩展功能,可以通过实现接口的方式扩展. - 可以理解实现接口是对 java 单继承机制的一种补充.
-
接口和继承解决的问题不同
- 继承的价值主要在于:解决代码的==复用性和可维护性==。
- 接口的价值主要在于:设计,设计好各种规范(方法),让其它类去实现这些方法。即更加的灵活.
-
接口比继承更加灵活
- 接口比继承更加灵活,继承是满足
is-a的 关系,而 接口只需满足like-a的关系。 - 接口在一定程度上实现代码解耦 ( 即:接
口规范性+动态绑定机制)
- 接口比继承更加灵活,继承是满足
10.8.8 接口的多态特性¶
-
多态参数
- 在前面的Usb接口案例,
Usblnterface usb f既可以接收手机对象,又可以接收相机对象,就体现了接口多态 - 接口引用可以指向实现了接口的类的对象
- 在前面的Usb接口案例,
-
多态数组
- 给Usb数组中,存放
Phone和camera对象,Phone类还有一个特有的方法call (),请遍历Usb数组,如果是Phone对象,除了调用Usb接口定义的方法外,还需要调用Phone特有方法call().
- 给Usb数组中,存放
-
接口多态传递现象
- 如果 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 内部类的分类¶
-
定义在外部类局部位置上¶
- (比如方法内):
- 局部内部类(有类名)
- 匿名内部类(没有类名,重点讲讲讲讲讲讲讲讲)
-
定义在外部类的成员位置上 :¶
- 成员内部类(没用static修饰)
- 静态内部类(使用static修饰)
10.9.5 局部内部类的使用¶
说明:局部内部类是定义在外部类的局部位置,比如方法中,并且有类名。
-
可以直接访问外部类的所有成员,包含私有的
-
不能添加访问修饰符(可以用
final修饰)- 因为它的地位就是一个局部变量。
- 局部变量是不能使用修饰符的。
- 但是可以使用final 修饰,因为局部变量也可以使用final
-
作用域:仅仅在定义它的方法或代码块中。
-
局部内部类
--- 访问 -->外部类的成员- 访问方式:直接访闻外部类
-
外部类
--- 访问 -->局部内部类的成员- 访问方式: 创建对象,再访问(注意:必须在作用域内)
-
外部其他类
--- 不能访问 -->局部内部类- (因为局部内部类地位是一个局部变量)
-
如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(
外部类名.this.成员)去访问System.out.println(外部类的 n2 = " +外部类名 .this.n2);
局部内部类定义在方法中/ 代码块 作用域在方法体或者代码块中 本质仍然是一个类
10.9.6 匿名内部类的使用¶
简要介绍¶
- 本质是类
- 内部类
- 该类没有名字
- 同时还是一个对象
说明:匿名内部类是定义在外部类的局部位置,比如方法中,并且没有类名
具体细节和使用¶
-
匿名内部类的基本语法
new 类 或接口(参数列表){类体} -
匿名内部类的语法比较奇特,请大家注意,因为匿名内部类既是一个类的定义,同时它本身也是一个对象,因此从语法上看,它脩福定义类的 特征,也有创建对象的特征,对前面代码分析可以看出这个特点,因此可以调用匿名内部类方法。
-
可以直接访问外部类的所有成员,包含私有的
-
不能添加访问修饰符,因为它的地位就是一介局部变量。
-
作用域:仅仅在定义它的方法或代码块中。
-
匿名内部类
--- 访问 -->外部类成员(访问方式:直接访问) -
外部其他类
--- 不能访问 -->匿名内部类(因为匿名内部类地位是一个局部变量) -
如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则,
-
如果想访问外部类的成员,则可以使用(
外部类名.this.成员)去访问
10.9.9 成员内部类的使用¶
说明:成员内部类是定义在外部类的成员位置,并 且没有static修饰。
-
可以直接访问外部类的所有成员,包含私有的
-
可以添加任意访问修饰符(public、protected 、默认、private),因为它的地位就是一个成员。
-
作用域: 和外部类的其他成员一样,为整个类体 比如前面案例,在外部类的成员方法中创建成员内部类对象,再调用方法.
-
成员内部类
--- 访问 -->外部类成员(比如:属性) 访问方式:直接访问 -
外部类
--- 访问 -->成员内部类 访问方式:创建对象,再访问 -
外部其他类
--- 访问 -->成员内部类 -
如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
outer08.new Inner08();相当于把 new Inner08()当做是 outer08 成员
10.9.10 静态内部类的使用¶
![[内部类#六、内部类的对比]]
10.10卖油翁和老黄牛¶
-
为什么构造器中不能初始化
static final变量?¶- 时间点不匹配:构造器在对象实例化时执行,而
static final变量必须在类加载的静态初始化阶段(早于对象创建)完成赋值。 - 语法限制:
final变量只能赋值一次,构造器的多次调用(如果创建多个对象)会导致尝试多次赋值,违反final规则。 - 编译器检查:Java 编译器会强制要求
static final变量在声明或静态代码块中初始化,构造器中的赋值尝试会导致编译错误,如“cannot assign a value to final variable”。
总结¶
- 饿汉式:
static final变量适合在声明时或静态代码块中初始化,构造器中不能初始化(语法不允许)。 - 懒汉式:直接使用
static final会导致无法延迟加载,构造器中也不能初始化。可以通过静态内部类结合static final实现延迟加载。 - 原因:
static final变量的初始化必须在类加载时完成,而构造器是实例级别的,时间点和作用域不匹配。
- 时间点不匹配:构造器在对象实例化时执行,而