一、成员变量(实例变量 + 静态变量)的初始化时机
Java 中成员变量的初始化时机分为实例变量和静态变量两类,规则不同:
1. 实例变量(非 static 成员变量)的初始化时机
实例变量属于 “对象级” 变量,随对象创建而初始化,时机有 3 个:
- 声明时直接赋值:
private int a = 10;(对象实例化时,构造器执行前完成); - 构造器中赋值:
this.a = 20;(声明时未赋值则在此初始化); - 初始化块({}):
{ a = 30; }(优先级介于声明赋值和构造器之间)。
核心:实例变量的初始化依赖对象实例化(必须调用构造器new对象),每创建一个对象,实例变量就重新初始化一次。
2. 静态变量(static 成员变量)的初始化时机
静态变量属于 “类级” 变量,随类加载而初始化,时机有 2 个:
- 声明时直接赋值:
private static int b = 100;(类加载阶段的 “准备 - 初始化” 环节完成); - 静态初始化块(static {}):
static { b = 200; }(与声明赋值的顺序一致,谁在前先执行谁)。
核心:静态变量的初始化只在类加载时执行一次,与对象实例化无关(即使不创建对象,类加载后静态变量也已存在)。
二、饿汉式 vs 懒汉式(单例模式的两种核心实现)
两者的本质区别是单例实例的创建时机:
1. 饿汉式:类加载时就创建实例(“饿”= 迫不及待)
public class Singleton {
// 类加载时直接初始化实例(饿汉式核心)
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE; // 直接返回已创建的实例
}
}
- 时机:类加载阶段(程序启动 / 首次使用类时)就创建实例;
- 特点:线程安全(类加载由 JVM 保证线程安全),但可能提前占用内存(即使暂时不用)。
2. 懒汉式:首次使用时才创建实例(“懒”= 拖延症)
public class Singleton {
// 类加载时仅声明,不初始化
private static Singleton INSTANCE;
private Singleton() {}
public static Singleton getInstance() {
if (INSTANCE == null) { // 首次调用时才创建
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
- 时机:首次调用
getInstance()方法时创建实例; - 特点:延迟加载(节省内存),但非线程安全(多线程同时调用可能创建多个实例),需加锁(如
synchronized)优化。
三、为什么private static final Singleton INSTANCE = new Singleton();不会递归?
关键在于静态变量的初始化时机 + 类加载的 “初始化锁” 机制:
1. 静态变量初始化是 “类加载阶段” 的操作,而非 “对象实例化阶段”
- 当 JVM 加载
Singleton类时,会先执行静态变量初始化:INSTANCE = new Singleton(); - 执行
new Singleton()时,会调用私有构造器Singleton(),但此时构造器内没有再次创建INSTANCE的逻辑(构造器是空的); - 构造器执行完毕后,
INSTANCE被赋值为唯一的实例,类加载完成。
整个过程是:类加载 → 静态变量初始化 → 调用构造器创建实例 → 构造器执行 → 实例赋值给静态变量,无递归。
2. 对比错误写法:private Singleton() { this.INSTANCE = new Singleton(); }
即使构造器是私有的,这种写法也会递归,原因是:
- 当
new Singleton()调用构造器时,构造器内执行this.INSTANCE = new Singleton(),这会再次调用构造器; - 新的构造器又会执行
new Singleton(),无限循环调用构造器,最终导致栈溢出(StackOverflowError); - 私有构造器仅限制 “外部类不能调用”,但类内部可以调用(包括构造器自己调用自己),因此私有性不影响递归的发生。
3. 核心区别:静态变量的 “唯一性” vs 实例变量的 “依赖性”
- 饿汉式的
INSTANCE是静态变量,类加载时仅初始化一次,且构造器内没有再次创建实例的逻辑,因此不会递归; - 错误写法中的
INSTANCE如果是实例变量(非 static),则每次创建对象都会初始化实例变量,而创建对象又依赖实例变量初始化,形成循环依赖,导致递归。
补充:懒汉式的单例模式为什么要加final
从语法上看,饿汉式去掉final也能运行(只要不主动修改INSTANCE),但:
- 不加
final会留下 “单例被破坏” 的隐患;static变量的作用域是类级别的,如果不加final,理论上类内部的其他方法可能意外修改INSTANCE的指向,破坏单例的唯一性。加上final后,INSTANCE的引用地址被固定,无法被重新赋值,从根本上杜绝了这种意外修改的可能。 - 加上
final是防御性编程的体现,符合单例模式 “唯一实例” 的核心约束。
2060

被折叠的 条评论
为什么被折叠?



