1. 概述
简单来说,在JVM上操作对象前,必须先完成初始化。本教程将探讨Java中初始化基本类型和对象的各种方式。
2. 声明与初始化
先明确两个概念:
声明是定义变量的过程,包含类型和名称:
int id;
初始化则是为变量赋值:
id = 1;
为演示区别,创建一个包含name
和id
属性的User
类:
public class User {
private String name;
private int id;
// 标准构造函数、getter、setter
}
接下来我们将看到,不同类型字段的初始化方式存在差异。
3. 对象与基本类型
Java提供两种数据表示:基本类型和引用类型。本节讨论两者在初始化上的区别。
Java有八种内置数据类型(基本类型),变量直接存储值。
引用类型存储对象的引用(类实例)。与基本类型不同,引用类型不直接存储对象值,而是通过存储对象所在内存地址指向对象。
注意:Java不允许直接获取物理内存地址,我们只能通过引用操作对象。
以下示例声明并初始化User
引用类型:
@Test
public void whenIntializedWithNew_thenInstanceIsNotNull() {
User user = new User();
assertThat(user).isNotNull();
}
可见,使用new
关键字可创建新对象并赋值给引用。下面深入探讨对象创建过程。
4. 创建对象
与基本类型不同,对象创建更复杂。因为不只是赋值,而是通过new
关键字触发初始化,进而调用构造函数并在内存中初始化对象。
new
关键字负责通过构造函数为新对象分配内存。
构造函数通常用于初始化表示对象主要属性的实例变量。
若未显式提供构造函数,编译器会创建无参默认构造函数,仅分配内存。
类可拥有多个构造函数(重载),只要参数列表不同。每个未调用同类其他构造函数的构造函数,都会隐式或显式调用父类构造函数(通过super()
)。
为User
类添加构造函数:
public User(String name, int id) {
this.name = name;
this.id = id;
}
现在可用构造函数创建带初始值的User
对象:
User user = new User("Alice", 1);
5. 变量作用域
以下章节探讨Java变量的不同作用域及其对初始化的影响。
5.1. 实例变量与类变量
实例变量和类变量无需显式初始化,声明时会自动获得默认值:
测试实例变量和类变量的默认值:
@Test
public void whenValuesAreNotInitialized_thenUserNameAndIdReturnDefault() {
User user = new User();
assertThat(user.getName()).isNull();
assertThat(user.getId() == 0);
}
5.2. 局部变量
局部变量必须在使用前初始化,它们没有默认值,编译器禁止使用未初始化的局部变量。
例如以下代码会触发编译错误:
public void print(){
int i;
System.out.println(i); // 编译错误
}
6. final关键字
final
修饰的字段表示初始化后值不可再修改,可用于定义常量。
为User
类添加常量:
private static final int YEAR = 2000;
常量必须在声明时或构造函数中初始化。
7. Java中的初始化块
Java中初始化块是无名称、无数据类型的代码块,位于方法、构造函数或其他代码块之外。
Java提供两种初始化块:静态初始化块和实例初始化块。
7.1. 实例初始化块
用于初始化实例变量。在User
类中通过实例初始化块设置id
:
{
id = 0;
}
7.2. 静态初始化块
静态初始化块(静态块)用于初始化static
字段,即带static
关键字的简单初始化块:
private static String forum;
static {
forum = "Java";
}
8. 初始化顺序
编写初始化代码时需注意初始化顺序:
- 静态变量和静态初始化块(按声明顺序)
- 实例变量和实例初始化块(按声明顺序)
- 构造函数
9. 对象生命周期
掌握对象创建后,我们来看看不再使用的对象会发生什么。
与其他语言不同,Java通过垃圾回收器自动管理对象销毁。
所有Java对象都存储在堆内存中,堆是为Java应用分配的大块未使用内存池。
垃圾回收器是Java程序,负责自动内存管理,通过删除不可达对象回收内存。
对象不可达的情况包括:
- ✅ 无任何引用指向对象
- ✅ 所有引用超出作用域
对象生命周期总结:通过new
创建 → 提供方法/字段访问 → 垃圾回收器销毁。
10. 其他创建对象的方法
除new
关键字外,还有反射、克隆和序列化等创建对象的方式。
反射是运行时检查类、字段和方法的机制。以下用反射创建User
对象:
@Test
public void whenInitializedWithReflection_thenInstanceIsNotNull()
throws Exception {
User user = User.class.getConstructor(String.class, int.class)
.newInstance("Alice", 2);
assertThat(user).isNotNull();
}
克隆是创建对象精确副本的方式。User
类需实现Cloneable
接口:
public class User implements Cloneable { //... }
使用clone()
创建副本:
@Test
public void whenCopiedWithClone_thenExactMatchIsCreated()
throws CloneNotSupportedException {
User user = new User("Alice", 3);
User clonedUser = (User) user.clone();
assertThat(clonedUser).isEqualTo(user);
}
也可使用sun.misc.Unsafe
类分配内存而不调用构造函数:
User u = (User) unsafeInstance.allocateInstance(User.class);
⚠️ 注意:Unsafe
是内部API,生产环境应谨慎使用。
11. 总结
本文全面探讨了Java字段初始化机制,分析了基本类型与引用类型的区别,展示了多种对象创建方式,并揭示了初始化顺序和对象生命周期。掌握这些知识能助你编写更健壮的Java代码。