Java 对象创建方式

面试官问:"Java 创建对象有哪些方式?"

候选人小程答:"可以用 new 关键字。"

面试官追问:"还有呢?"

小程说:"反射?clone?"

面试官追问:"具体怎么用?有什么区别?"

小程答不全。

【面试官心理】 这道题看似简单,但能完整说出五种方式并理解其适用场景的候选人不多。这是 Java 基础的综合题,考查候选人的知识广度。

一、创建对象的五种方式 🔴

方式关键字/方法适用场景
newnew Person()最常用
反射Class.newInstance() / Constructor.newInstance()框架、IOC
cloneobj.clone()原型模式
序列化ObjectInputStream.readObject()深拷贝、RPC
UnsafeUnsafe.allocateInstance()高性能、跳过构造器

二、new 关键字 🔴

// 最常见的创建方式
Person person = new Person("Alice", 30);

// 编译后字节码:
// new Person           // 在堆中分配内存,创建对象
// dup                  // 复制引用(用于后续调用)
// ldc "Alice"          // 加载常量
// bipush 30           // 加载整数
// invokespecial Person.<init>  // 调用构造器
// astore_1            // 存储到局部变量表

过程

  1. 加载类(如果还没加载)
  2. 在堆中分配内存
  3. 设置对象头(Mark Word、Klass 指针、数组长度)
  4. 调用构造器初始化字段

三、反射创建对象 🔴

3.1 Class.newInstance()(JDK 9 废弃)

// JDK 8
Class<Person> clazz = Person.class;
Person p = clazz.newInstance(); // 调用无参构造器

// 缺点:
// 1. 只能调用无参构造器
// 2. 权限检查:private 构造器无法调用
// 3. JDK 9+ 废弃

3.2 Constructor.newInstance()(推荐)

Class<Person> clazz = Person.class;

// 无参构造器
Person p1 = clazz.getDeclaredConstructor().newInstance();

// 有参构造器
Constructor<Person> ctor = clazz.getDeclaredConstructor(String.class, int.class);
Person p2 = ctor.newInstance("Alice", 30);

// 可以访问 private 构造器
Constructor<Person> privateCtor = clazz.getDeclaredConstructor(String.class);
privateCtor.setAccessible(true);
Person p3 = privateCtor.newInstance("Bob");

优势

  • 可以调用任意构造器(包括 private)
  • 支持有参构造器
  • 是 Java 反射 API 的标准方式

3.3 Spring 中的反射

// Spring IOC 使用 Constructor.newInstance() 创建 Bean
// BeanWrapperImpl 中:
public void createBean(Class<?> beanClass) {
    Constructor<?> ctor = BeanUtils.findPrimaryConstructor(beanClass);
    reflectionSupport.makeAccessible(ctor);
    return ctor.newInstance(args);
}

四、clone() 方法 🔴

class Person implements Cloneable {
    String name;
    Person(String name) { this.name = name; }

    @Override
    protected Person clone() throws CloneNotSupportedException {
        return (Person) super.clone(); // 调用 Object.clone()
    }
}

Person p1 = new Person("Alice");
Person p2 = p1.clone(); // 不需要调用构造器

特点

  • 不需要调用构造器
  • 默认是浅拷贝
  • 必须实现 Cloneable 接口
  • 性能好于反射

五、序列化创建对象 🔴

public class DeepCopyUtil {
    public static <T extends Serializable> T deepCopy(T obj) {
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectObjectStream(bos);
            oos.writeObject(obj);
            oos.close();

            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            @SuppressWarnings("unchecked")
            T copy = (T) ois.readObject();
            return copy;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

特点

  • 不需要目标类实现任何接口(除了 Serializable)
  • 自动深拷贝整个对象图
  • 性能较慢

六、Unsafe 创建对象 🔴

import sun.misc.Unsafe;

Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);

// 方式一:分配内存但不调用构造器
Person person = (Person) unsafe.allocateInstance(Person.class);
// person.name = null(构造器未执行!)

// 方式二:直接分配指定大小的内存
long address = unsafe.allocateMemory(16); // 分配 16 字节

特点

  • 不调用构造器
  • 可以分配任意大小的内存
  • 用于高性能框架(Dubbo、Netty、FastJSON)
⚠️

Unsafe.allocateInstance() 不会执行构造器,字段保持零值。这是危险的,但有时也是有用的(如 Hessian 序列化框架需要跳过构造器)。

七、性能对比 🟡

方式性能构造器调用反射开销
new最快
clone
Unsafe最快(跳过构造器)
Constructor.newInstance高(安全检查)
序列化最慢✅(反序列化时)

八、生产中的选择 🟡

// 场景一:普通业务代码
Person p = new Person("Alice", 30); // ✅ 推荐

// 场景二:框架创建 Bean
// Spring IOC 使用 Constructor.newInstance()
// MyBatis 使用 Constructor.newInstance()

// 场景三:原型模式
public class PrototypeManager {
    private Map<String, Cloneable> prototypes = new HashMap<>();

    public Cloneable getClone(String key) {
        return prototypes.get(key).clone();
    }
}

// 场景四:高性能序列化
// FastJSON 使用 Unsafe + 手动字段设置
// Kryo 使用低拷贝序列化

九、追问升级

面试官:"一个对象创建需要经历哪些步骤?"

// 1. 类加载检查
//    如果类还没加载,先进行类加载(加载-验证-准备-解析-初始化)

// 2. 分配内存
//    - 指针碰撞(内存规整):CAS 更新指针
//    - 空闲列表(内存碎片):维护空闲块列表

// 3. 设置对象头
//    Mark Word: 哈希码、GC 年龄、锁状态
//    Klass 指针: 指向方法区的类元数据
//    数组长度: 如果是数组

// 4. 初始化字段
//    设置零值(零值初始化)

// 5. 执行构造器
//    调用 <init> 方法

【面试官心理】 能说出对象创建完整步骤(类加载检查→内存分配→对象头设置→零值初始化→构造器)的候选人,说明对 JVM 内存模型有深入理解。这是 P6+ 的要求。