浅拷贝与深拷贝

面试官问:"浅拷贝和深拷贝有什么区别?"

候选人小冯答:"浅拷贝只复制引用,深拷贝复制整个对象。"

面试官点点头:"那 Java 怎么实现深拷贝?"

小冯说:"可以用 clone() 方法,或者序列化..."

面试官追问:"如果对象里有引用类型,怎么保证 clone() 是深拷贝而不是浅拷贝?"

小冯答不上来。

【面试官心理】 这道题考查的是候选人对对象复制机制的理解深度。能说出 clone() 方法需要手动处理引用字段的候选人,说明真正理解过浅拷贝和深拷贝的区别。

一、基本概念 🔴

1.1 浅拷贝(Shallow Copy)

class Address {
    String city;
    Address(String city) { this.city = city; }
}

class Person implements Cloneable {
    String name;
    Address address; // 引用类型

    public Person(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    @Override
    protected Person clone() {
        try {
            return (Person) super.clone(); // 默认是浅拷贝
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}

Person p1 = new Person("Alice", new Address("Beijing"));
Person p2 = p1.clone();

// 浅拷贝结果:
// p1.name = "Alice", p1.address = Address@123
// p2.name = "Alice", p2.address = Address@123 ← 同一个对象!

// 修改 p2 的引用类型字段会影响 p1:
p2.address.city = "Shanghai";
System.out.println(p1.address.city); // Shanghai ← 被影响了!

1.2 深拷贝(Deep Copy)

class Person implements Cloneable {
    String name;
    Address address;

    @Override
    protected Person clone() {
        try {
            Person cloned = (Person) super.clone();
            // ✅ 深拷贝:手动克隆引用字段
            cloned.address = this.address.clone();
            return cloned;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}

// 深拷贝结果:
// p1.address = Address@123
// p2.address = Address@456 ← 新对象!

// 修改 p2 的引用类型字段不影响 p1:
p2.address.city = "Shanghai";
System.out.println(p1.address.city); // Beijing ← 没被影响

二、Cloneable 接口的局限性 🔴

2.1 Cloneable 的设计缺陷

// Cloneable 是一个"标记接口",没有任何方法
// 它的问题是:clone() 方法不是 Cloneable 定义的!

public interface Cloneable { }

// Object.clone() 是这样工作的:
// protected native Object clone() throws CloneNotSupportedException;
// 只有实现了 Cloneable,Object.clone() 才返回字段拷贝
// 否则抛 CloneNotSupportedException

class BadClass { }
BadClass obj = new BadClass();
obj.clone(); // ❌ CloneNotSupportedException(没有实现 Cloneable)

2.2 clone() 的坑

// 坑一:返回 Object,需要强制类型转换
Person p = (Person) super.clone();

// 坑二:必须处理受检异常
try {
    return (Person) super.clone();
} catch (CloneNotSupportedException e) {
    // ...
}

// 坑三:final 字段无法深拷贝
class Person implements Cloneable {
    String name;
    final Address address = new Address(); // final 字段
    @Override
    protected Person clone() {
        Person cloned = (Person) super.clone();
        // ❌ 无法为 final 字段重新赋值
        // cloned.address = this.address.clone(); // 编译错误
        return cloned;
    }
}

// 坑四:引用链深层拷贝
class A {
    B b;
}
class B {
    C c;
}
class C {
    String data;
}
// 深拷贝 A 需要同时克隆 B 和 C,一层层嵌套

三、深拷贝的常用方式 🔴

3.1 手动克隆(需要所有类配合)

class Address implements Cloneable {
    String city;
    @Override
    protected Address clone() { return new Address(this.city); }
}

class Person implements Cloneable {
    String name;
    Address address;
    @Override
    protected Person clone() {
        Person cloned = (Person) super.clone();
        cloned.address = this.address.clone();
        return cloned;
    }
}

3.2 序列化(JSON / Java Serializable)

// 方式一:JSON 序列化(推荐)
public static <T> T deepCopy(T obj) {
    ObjectMapper mapper = new ObjectMapper();
    return mapper.readValue(mapper.writeValueAsString(obj), (Class<T>) obj.getClass());
}

// 方式二:Java 序列化
public static <T extends Serializable> T deepCopy(T obj) {
    try {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(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 IllegalArgumentException("Clone failed", e);
    }
}

3.3 Apache Commons 工具

// Apache Commons Lang
public static <T> T deepClone(T obj) {
    try {
        byte[] bytes = SerializationUtils.serialize((Serializable) obj);
        return (T) SerializationUtils.deserialize(bytes);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

// Apache Commons BeanUtils
BeanUtils.cloneProperties(target, source); // 浅拷贝
BeanUtils.copyProperties(target, source);  // 浅拷贝
// 深拷贝需要自定义转换器

3.4 Google Guava 工具

// Guava 的深拷贝
public static <T> T deepCopy(T obj) {
    return new Gson().fromJson(new Gson().toJson(obj), obj.getClass());
}

四、性能对比 🟡

方式性能依赖限制
手动 clone最快所有类实现 Cloneable深层对象复杂
JSON 序列化慢(序列化开销)Jackson/Gson需要无参构造器
Java 序列化JDK 内置所有对象实现 Serializable
Apache Commons中等commons-lang需要 Serializable

五、生产中的选择 🟡

// 场景一:DTO 拷贝(字段简单)
UserDTO dto = modelMapper.map(user, UserDTO.class);

// 场景二:复杂对象图
Person deepCopy = new Gson().fromJson(new Gson().toJson(person), Person.class);

// 场景三:需要高性能
// 使用手动 clone() 或 proto-protobuf 等高性能序列化

// 场景四:需要保留对象关系
// 例如:树形结构节点间有父子引用
// 深拷贝时需要手动维护引用关系

六、追问升级

面试官:"为什么阿里巴巴 Java 规约不推荐使用 clone() 方法?"

// 阿里巴巴 Java 开发规约:不推荐使用 clone()
// 原因:
// 1. clone() 是 Object 的 protected 方法,调用不便
// 2. CloneNotSupportedException 是受检异常
// 3. 字段逐个拷贝,不够灵活
// 4. 无法拷贝 final 字段
// 5. 类型安全问题(强制类型转换)

// ✅ 推荐的替代方案:
// 1. 构造器拷贝
new Person(existing.getName(), existing.getAddress().clone())

// 2. 工厂方法
Person.from(other)

// 3. 序列化工具
SerializationUtils.clone(obj)

【面试官心理】 能说出阿里巴巴规约不推荐 clone() 的原因的候选人,说明有代码规范意识,并且理解过替代方案。这是 P6 的要求。