对象实例化方式
创建对象的方式有哪些
很多人以为 new 是创建对象的唯一方式。实际上,Java 中创建对象有多种方式,new 只是最常见的一种。
常见创建方式
1. new 关键字(最常用)
java
public class NewObject {
public static void main(String[] args) {
// 最常见的方式
Student s = new Student();
}
}2. 反射:Class.newInstance()(已淘汰)
java
public class NewInstanceDemo {
public static void main(String[] args) throws Exception {
// JDK 9 之前推荐的方式
Student s = Student.class.newInstance();
// 等价于 new Student()
}
}newInstance() 实际上调用的是无参构造器,而且会抛出已检查异常,已被 Constructor.newInstance() 替代。
3. 反射:Constructor.newInstance()(推荐)
java
public class ConstructorNewInstance {
public static void main(String[] args) throws Exception {
// 获取构造器
Constructor<Student> constructor = Student.class.getConstructor(String.class, int.class);
// 调用有参构造器
Student s = constructor.newInstance("Alice", 20);
}
}Constructor.newInstance() 是目前反射创建对象的推荐方式:
- 支持调用有参构造器
- 支持调用私有构造器(
setAccessible(true)) - 对已检查异常更友好
4. clone() 方法
java
public class CloneDemo {
public static void main(String[] args) throws CloneNotSupportedException {
Student original = new Student("Bob", 22);
// clone() 需要类实现 Cloneable 接口
// 并且重写 Object.clone()
Student copy = (Student) original.clone();
// 关键:clone() 创建的是原对象的副本
// 不会调用任何构造器
// 两个对象是独立的存在
System.out.println(copy == original); // false
}
}
class Student implements Cloneable {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone(); // 浅拷贝
}
}clone() 不会调用任何构造器,复制的是对象的字节流——这是一种浅拷贝。
5. 反序列化
java
public class SerializationDemo {
public static void main(String[] args) throws Exception {
Student original = new Student("Carol", 25);
// 序列化到字节数组
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(original);
oos.close();
// 反序列化创建新对象
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
Student copy = (Student) ois.readObject();
// 反序列化不会调用构造器
// 创建的是堆中全新的对象
System.out.println(copy == original); // false
}
}6. Unsafe.allocateInstance()(底层方式)
java
public class UnsafeAllocDemo {
public static void main(String[] args) throws Exception {
// 直接在堆中分配内存,不调用构造器
Unsafe unsafe = getUnsafe();
// 在堆中分配 Student 大小的内存,但不调用任何构造器
Student s = (Student) unsafe.allocateInstance(Student.class);
// 此时 s 的字段全是默认值(name=null, age=0)
System.out.println(s.getName()); // null
System.out.println(s.getAge()); // 0
}
}Unsafe.allocateInstance() 是最底层的对象创建方式,完全跳过构造器,对象字段全是默认值。
典型使用场景:高性能对象池(如 Disruptor)、延迟构造。
创建方式的对比
| 方式 | 是否调用构造器 | 调用链 | 典型场景 |
|---|---|---|---|
new | ✅ 是 | 构造器 | 日常开发 |
Constructor.newInstance() | ✅ 是 | 构造器 | 反射 |
Class.newInstance() | ✅ 是 | 无参构造器 | JDK 8 及之前 |
clone() | ❌ 否 | 不调用 | 对象复制 |
反序列化 | ❌ 否 | 不调用 | 对象持久化 |
Unsafe.allocateInstance() | ❌ 否 | 不调用 | 高性能/对象池 |
底层创建过程(new 关键字)
无论用哪种方式创建对象,new 字节码指令的核心步骤是相同的:
new 关键字
│
▼
检查类是否已加载(未加载 → 类加载)
│
▼
在堆中分配内存
│
├── TLAB:线程本地分配缓冲
└── 指针碰撞:Eden 区有空闲指针
│
▼
零值初始化(所有字段设为默认值)
│
▼
设置对象头(Mark Word + 类型指针)
│
▼
执行 `<init>` 方法(构造器)
│
▼
返回对象引用对象创建的完整字节码过程
用 javap -c 看看 new 的字节码:
java
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}编译后:
java
public static void main(java.lang.String[]);
Code:
0: new #3 // new Student,分配内存
3: dup // 复制引用(供构造器使用)
4: ldc #4 // 加载字符串常量 "Alice"
6: bipush 20 // 加载整数常量 20
8: invokespecial #5 // 调用构造器 Student()
11: astore_1 // 存到局部变量
12: return本节小结
Java 中有 6 种对象创建方式:
new:最常用- 反射(Constructor):最灵活
clone():对象复制- 反序列化:持久化/网络传输
Unsafe.allocateInstance():跳过构造器,高性能场景
理解这些创建方式,有助于理解框架底层(如 Spring 如何通过反射创建 Bean、MyBatis 如何通过构造器创建对象实例)。
下一节,我们来看 字节码视角:对象创建过程,深入理解 new 字节码指令的完整执行过程。
