java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java中Clonable接口和深拷贝

关于Java中Clonable接口和深拷贝详解

作者:Porunarufu

Cloneable是一个接口,实现这个接口后,可以在类中重写Object中的clone方法,然后通过类调用clone方法进行克隆,这篇文章主要介绍了关于Java中Clonable接口和深拷贝的相关资料,需要的朋友可以参考下

前言

在 Java 中,Cloneable 接口是实现对象拷贝的核心机制之一,但它默认仅支持 浅拷贝;而 深拷贝 是基于浅拷贝的进阶需求,用于解决引用类型成员共享的问题

一、先搞懂:Cloneable接口是什么?

1. 核心定位:标记接口(Marker Interface)

Cloneable 是 Java 中的 标记接口(无任何抽象方法),仅用于告诉 JVM:“这个类的对象允许被克隆(clone())”

2. 浅拷贝(Shallow Copy):Cloneable的默认行为

(1)浅拷贝的定义

当对象被浅拷贝时,会创建一个 新的对象实例,但对象中的 引用类型成员变量 不会被复制(新对象和原对象共享同一个引用类型成员)。

(2)实现浅拷贝的3 个步骤

  1. 类实现 Cloneable 接口(标记允许克隆);
  2. 重写 Object 类的 clone() 方法(提升访问权限为 public,处理异常);
  3. 调用 super.clone() 完成浅拷贝(JVM 原生实现对象拷贝)。

(3)代码示例:浅拷贝实战

// 引用类型:地址(被 User 类引用)
class Address {
    private String city;

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

    // getter/setter
    public String getCity() { return city; }
    public void setCity(String city) { this.city = city; }
}

// 实现 Cloneable 接口,支持浅拷贝
class User implements Cloneable {
    private String name; // 基本类型包装类(不可变,浅拷贝无问题)
    private int age;     // 基本类型(浅拷贝直接复制值)
    private Address addr;// 引用类型(浅拷贝仅复制引用,共享对象)

    // 构造方法
    public User(String name, int age, Address addr) {
        this.name = name;
        this.age = age;
        this.addr = addr;
    }

    // 重写 clone() 方法:实现浅拷贝
    @Override
    public User clone() throws CloneNotSupportedException {
        // 调用 Object 的 clone(),JVM 会创建新对象并复制成员值
        return (User) super.clone();
    }

    // getter/setter
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
    public Address getAddr() { return addr; }
}

// 测试浅拷贝
public class ShallowCopyTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        // 1. 创建原对象
        Address addr = new Address("北京");
        User user1 = new User("张三", 20, addr);

        // 2. 浅拷贝得到新对象
        User user2 = user1.clone();

        // 3. 验证:基本类型/不可变类型的拷贝(独立)
        System.out.println(user1.getName() == user2.getName()); // true(String 不可变,共享无问题)
        user2.setName("李四");
        user2.setAge(22);
        System.out.println(user1.getName()); // 张三(原对象不受影响)
        System.out.println(user1.getAge());  // 20(原对象不受影响)

        // 4. 验证:引用类型的拷贝(共享)
        System.out.println(user1.getAddr() == user2.getAddr()); // true(同一个 Address 对象)
        user2.getAddr().setCity("上海"); // 修改 user2 的 Address
        System.out.println(user1.getAddr().getCity()); // 上海(原对象的 Address 也被修改!)
    }
}

(4)浅拷贝的问题

当对象包含 可变的引用类型成员(如 Address)时,浅拷贝会导致原对象和拷贝对象共享该成员,修改其中一个会影响另一个,破坏对象的 “独立性”—— 这就是需要深拷贝的场景。

二、深拷贝(Deep Copy):解决引用类型共享问题

1. 深拷贝的定义

深拷贝会创建一个 完全独立的新对象:不仅拷贝对象本身,还会递归拷贝对象中所有 可变的引用类型成员变量,最终新对象和原对象的引用类型成员指向不同的内存地址,互不影响。

2. 深拷贝的 3 种实现方式(实战常用)

方式 1:重写clone()方法,手动递归拷贝引用类型

核心思路:在浅拷贝的基础上,对每个引用类型成员也调用其 clone() 方法(需让引用类型也实现 Cloneable)。

// 步骤1:让引用类型 Address 也实现 Cloneable,支持拷贝
class Address implements Cloneable {
    private String city;

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

    // 重写 clone() 方法
    @Override
    public Address clone() throws CloneNotSupportedException {
        return (Address) super.clone();
    }

    // getter/setter 略
}

// 步骤2:User 类的 clone() 中,手动拷贝 Address
class User implements Cloneable {
    private String name;
    private int age;
    private Address addr;

    // 构造方法略

    @Override
    public User clone() throws CloneNotSupportedException {
        // 第一步:浅拷贝 User 对象本身
        User newUser = (User) super.clone();
        // 第二步:手动拷贝引用类型成员(深拷贝核心)
        newUser.addr = this.addr.clone(); // 递归拷贝 Address
        return newUser;
    }

    // getter/setter 略
}

// 测试深拷贝
public class DeepCopyTest1 {
    public static void main(String[] args) throws CloneNotSupportedException {
        Address addr = new Address("北京");
        User user1 = new User("张三", 20, addr);
        User user2 = user1.clone();

        // 验证:引用类型成员不再共享
        System.out.println(user1.getAddr() == user2.getAddr()); // false(不同 Address 对象)
        user2.getAddr().setCity("上海");
        System.out.println(user1.getAddr().getCity()); // 北京(原对象不受影响)
        System.out.println(user2.getAddr().getCity()); // 上海(新对象独立修改)
    }
}

方式 2:通过序列化(Serializable)实现深拷贝

核心思路:利用 Java 序列化将对象写入流(序列化)再从流中读取(反序列化),生成的新对象是完全独立的(无需手动递归拷贝)。

import java.io.*;

// 步骤1:所有相关类实现 Serializable 接口(标记可序列化)
class Address implements Serializable {
    private String city;
    public Address(String city) { this.city = city; }
    // getter/setter 略
}

class User implements Serializable {
    private String name;
    private int age;
    private Address addr;

    public User(String name, int age, Address addr) {
        this.name = name;
        this.age = age;
        this.addr = addr;
    }

    // 步骤2:实现深拷贝工具方法(序列化+反序列化)
    public User deepCopy() throws IOException, ClassNotFoundException {
        // 1. 序列化:将对象写入字节流
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this); // 写入当前 User 对象

        // 2. 反序列化:从字节流读取新对象
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        return (User) ois.readObject(); // 生成独立的新对象
    }

    // getter/setter 略
}

// 测试序列化深拷贝
public class DeepCopyTest2 {
    public static void main(String[] args) throws Exception {
        Address addr = new Address("北京");
        User user1 = new User("张三", 20, addr);
        User user2 = user1.deepCopy();

        // 验证:完全独立
        System.out.println(user1.getAddr() == user2.getAddr()); // false
        user2.getAddr().setCity("上海");
        System.out.println(user1.getAddr().getCity()); // 北京
        System.out.println(user2.getAddr().getCity()); // 上海
    }
}

方式 3:使用第三方工具(简化开发)

实际开发中,可借助成熟工具类避免重复编码,常用的有:

示例(Apache Commons Lang):

  1. 引入依赖(Maven):
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.14.0</version>
    </dependency>
  2. 代码实现:
    import org.apache.commons.lang3.SerializationUtils;
    
    // 所有类仍需实现 Serializable
    class User implements Serializable { /* 成员变量、构造方法略 */ }
    
    public class DeepCopyTest3 {
        public static void main(String[] args) {
            Address addr = new Address("北京");
            User user1 = new User("张三", 20, addr);
            // 一行代码实现深拷贝
            User user2 = SerializationUtils.clone(user1);
    
            System.out.println(user1.getAddr() == user2.getAddr()); // false
        }
    }

3. 深拷贝的注意事项

三、浅拷贝 vs 深拷贝 核心区别

四、常见面试题 & 实战建议

1. 面试高频问题

(1)Cloneable接口有什么用?如果不实现它,调用clone()会怎样?

(2)Object类的clone()方法是浅拷贝还是深拷贝?

默认是 浅拷贝仅复制对象的成员值(基本类型复制值,引用类型复制引用地址)

(3)深拷贝的实现方式有哪些?各自的优缺点?

(4)为什么String类型在浅拷贝中不会有问题?

String 是 不可变类型(一旦创建无法修改,修改时会生成新 String 对象),浅拷贝时共享引用不会导致原对象被修改,因此无需深拷贝。

2. 实战选择建议

总结

到此这篇关于关于Java中Clonable接口和深拷贝的文章就介绍到这了,更多相关Java中Clonable接口和深拷贝内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文