1. 什么是初始化块
在 Java 中有两种初始化块:
- 非静态(实例)初始化块 — 在每次创建新对象时执行,先于构造函数、紧跟在字段初始化之后。
- 静态初始化块 — 在类被加载进内存时执行一次(先于创建第一个对象或访问静态字段/方法)。
非静态初始化块
直接在类体中声明,不需要使用static 等关键字:
public class User {
private String name;
// 非静态初始化块
{
System.out.println("正在执行非静态初始化块!");
name = "默认名称";
}
public User() {
System.out.println("正在执行构造函数!");
}
}
静态初始化块
使用关键字 static 声明:
public class Config {
public static String appName;
static {
System.out.println("正在执行静态初始化块!");
appName = "我的超级应用";
}
}
2. 初始化顺序:谁先谁后?
在 Java 中,类中各元素的初始化顺序不是“随意而为”,而是有严格规定的。如果你曾经不看说明书就装过 IKEA 的家具,你就会明白为什么顺序很重要:如果步骤搞错了,柜子可能会变成装置艺术。
初始化顺序:
- 静态字段与静态块 — 按它们在类中声明的先后顺序执行。类加载时只执行一次。
- 非静态字段与非静态块 — 按声明顺序,在每次创建新对象时执行。
- 构造函数 — 在所有非静态初始化之后执行。
示意图
+-------------------------------+
| 在 JVM 中加载类 |
+-------------------------------+
| 1. 静态字段 |
| 2. 静态初始化块 |
| ↓ |
| 创建对象 |
| ↓ |
| 3. 非静态字段 |
| 4. 非静态初始化块 |
| 5. 构造函数 |
+-------------------------------+
带输出的示例
我们来写一个类,直观地展示一切发生的先后顺序:
public class Demo {
static String staticField = print("1. static 字段");
static {
print("2. static 块");
}
String field = print("3. 非静态字段");
{
print("4. 非静态块");
}
public Demo() {
print("5. 构造函数");
}
static String print(String msg) {
System.out.println(msg);
return msg;
}
public static void main(String[] args) {
System.out.println("创建第一个 Demo 对象:");
Demo d1 = new Demo();
System.out.println("\n创建第二个 Demo 对象:");
Demo d2 = new Demo();
}
}
屏幕上会看到什么?
1. static 字段
2. static 块
创建第一个 Demo 对象:
3. 非静态字段
4. 非静态块
5. 构造函数
创建第二个 Demo 对象:
3. 非静态字段
4. 非静态块
5. 构造函数
请注意: 静态部分(static)只会执行一次——在首次访问该类时。所有非 static 的内容,会在每次创建对象时执行。
3. 代码示例:为什么需要初始化块
当构造函数不够用时
有时,部分初始化需要对所有构造函数通用。比如一个类有多个构造函数,而你不想在每个构造函数里都重复相同的初始化代码。此时可以将其提取到非静态初始化块中:
public class Person {
private String id;
private String name;
{
// 这段代码会在任何构造函数之前执行
id = java.util.UUID.randomUUID().toString();
System.out.println("正在生成唯一 id: " + id);
}
public Person() {
System.out.println("Person() 无参数");
}
public Person(String name) {
this.name = name;
System.out.println("Person(String name)");
}
}
结果: 创建任何 Person 对象时,会先生成 id,然后再执行相应的构造函数。
初始化复杂的静态数据
静态块常用于初始化“重量级”或复杂的静态字段,例如从文件读取配置、创建集合、连接数据库等。
public class Settings {
public static final java.util.Map<String, String> DEFAULTS;
static {
DEFAULTS = new java.util.HashMap<>();
DEFAULTS.put("theme", "light");
DEFAULTS.put("language", "ru");
System.out.println("Settings 静态块:默认设置");
}
}
4. 一些有用的细节
何时使用初始化块
适合使用的场景
- 用于所有构造函数都需要的通用初始化。
- 用于无法通过简单赋值表达的复杂静态数据。
- 用于初始化静态资源(例如应用启动时读取配置文件)。
不适合使用的场景
- 如果可以用普通赋值或构造函数解决——就用它们。
- 不要把业务逻辑“藏”在初始化块里——这会降低代码的可读性与可维护性。
- 如果初始化依赖构造函数的参数,请在构造函数中完成。
不要滥用初始化块
初始化块是强大的工具,但并不常用。大多数情况下,普通赋值或构造函数就足够了。如果类里有太多的初始化块,代码会变得难以阅读和维护。
不要用非静态初始化块处理依赖构造参数的逻辑
在非静态初始化块中无法使用构造函数的参数,因为它在构造函数之前执行。如果需要基于参数进行初始化,请在构造函数中完成。
静态初始化块与继承
静态初始化块不会被继承。每个类都有自己的静态块。加载子类时,会先执行基类的静态块,再执行子类的静态块。
5. 使用初始化块的常见错误
错误 №1:期望非静态块能看到构造函数参数。
很多初学者尝试在非静态块中使用构造函数参数,但会得到编译错误或意外结果。请记住:非静态块在构造函数之前执行,也就是说当时还没有任何参数。
错误 №2:在初始化块中放入过多逻辑。
如果初始化块里出现复杂逻辑,代码会变得混乱。最好在构造函数或单独的方法中完成主要工作。
错误 №3:多个静态块的声明顺序不同导致问题。
如果类中有多个静态块,它们会与静态字段一起按代码中的声明顺序执行。如果一个静态块依赖另一个的结果,可能会导致意外结果。
错误 №4:认为静态块会在每次创建对象时执行。
static 块只会执行一次——在类加载时。如果你指望再次初始化,这是不会发生的。
错误 №5:尝试在静态块中访问非静态字段。
在静态块中只能访问静态变量和方法。试图访问非静态(实例)字段会导致编译错误。
GO TO FULL VERSION