Java 深克隆 和 浅克隆

本文详细介绍了Java中的对象克隆,包括浅克隆和深克隆的概念及区别。通过实例展示了如何在Java中实现这两种克隆方法,包括重写`clone()`方法以及使用构造函数实现深克隆。同时,讨论了`Object`类的`clone()`方法的约定以及`Arrays.copyOf()`方法在克隆数组时的行为。最后,总结了深克隆的不同实现方式,如自定义克隆方法、使用字节流以及JSON工具类。
摘要由CSDN通过智能技术生成

一、Shadow Clone 浅克隆

是把原型对象中成员变量为值类型的属性都复制给克隆对象,把原型对象中成员变量为引用类型的引用地址也复制给克隆对象,也就是原型对象中如果有成员变量为引用对象,则此引用对象的地址是共享给原型对象和克隆对象的。

简单来说就是浅克隆只会复制原型对象,但不会复制它所引用的对象,如下图所示:
浅克隆1


二、Deep Clone 深克隆

是将原型对象中的所有类型,无论是值类型还是引用类型,都复制一份给克隆对象,也就是说深克隆会把原型对象和原型对象所引用的对象,都复制一份给克隆对象,如下图所示:
深克隆1


三、Java 实现克隆

需要实现 Cloneable 接口,并重写 Object 类中的 clone() 方法,实现代码如下:

/**
 * author:wy
 * describe:Java 实现克隆
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class People implements Cloneable {

    public static void main(String[] args) {
        People p1 = new People();
        p1.setId(1);
        p1.setName("Java");

        People p2 = null;
        try {
            // 克隆p1对象
            p2 = (People) p1.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        
        System.out.println(p1);// People(id=1, name=Java)
        System.out.println(p2);// People(id=1, name=Java)

        System.out.println(p1 == p2);// false
        System.out.println(p1.getClass() == p2.getClass());// true
        System.out.println(p1.equals(p2));// true

        p1.setName("Android");
        System.out.println(p1);// People(id=1, name=Android)
        System.out.println(p2);// People(id=1, name=Java)
    }

    // 属性
    private Integer id;
    private String name;

    /**
     * 重写 Object 类中的 clone() 方法
     * @return
     * @throws CloneNotSupportedException
     */
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

四、知识扩展


1. clone() 深克隆

// 是使用 native 修饰的本地方法,因此执行的性能会很高
protected native Object clone() throws CloneNotSupportedException;

从以上源码的注释信息中我们可以看出,Object 对 clone() 方法的约定有三条:

  1. 对于所有对象来说,x.clone() !=x 应当返回 true,因为克隆对象与原对象不是同一个对象;
  2. 对于所有对象来说,x.clone().getClass() == x.getClass() 应当返回 true,因为克隆对象与原对象的类型是一样的;
  3. Ba对于所有对象来说,x.clone().equals(x) 应当返回 true,因为使用 equals 比较时,它们的值都是相同的。

2. Arrays.copyOf() 浅克隆

@Test
public void test() {
    People[] o1 = {new People(1, "Java")};
    People[] o2 = Arrays.copyOf(o1, o1.length);
    // 修改原型对象的第一个元素的值
    o1[0].setName("Android");
    System.out.println(o1[0]);// People(id=1, name=Android)
    System.out.println(o2[0]);// People(id=1, name=Android)
}

因为数组比较特殊,数组本身就是引用类型,因此在使用 Arrays.copyOf() 其实只是把引用地址复制了一份给克隆对象,如果修改了它的引用对象,那么指向它的(引用地址)所有对象都会发生改变,因此看到的结果是,修改了克隆对象的第一个元素,原型对象也跟着被修改了。


3. 深克隆实现方式汇总

深克隆的实现方式有很多种,大体可以分为以下几类:

  1. 所有对象都实现克隆方法;
  2. 通过构造方法实现深克隆;
  3. 使用 JDK 自带的字节流实现深克隆;
  4. 使用第三方工具实现深克隆,比如 Apache Commons Lang;
  5. 使用 JSON 工具类实现深克隆,比如 Gson、FastJSON 等。

3.1 所有对象都实现克隆
/**
 * author:wy
 * describe:收货人
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Consignee implements Cloneable {

    public static void main(String[] args) throws CloneNotSupportedException {
        Address address = new Address(101, "上海");
        Consignee c1 = new Consignee(1, "骑士", address);
        // 克隆 c1 对象
        Consignee c2 = c1.clone();

        // 修改原型对象
        c1.getAddress().setCity("北京");
        System.out.println(c1);// Consignee(id=1, name=骑士, address=Address(id=101, city=北京))
        System.out.println(c2);// Consignee(id=1, name=骑士, address=Address(id=101, city=上海))
    }

    private Integer id;
    private String name;
    private Address address;// 包含 Address 对象

    @Override
    protected Consignee clone() throws CloneNotSupportedException {
        Consignee consignee = (Consignee) super.clone();
        consignee.setAddress(this.address.clone());// 引用类型克隆赋值
        return consignee;
    }
}
/**
 * author:wy
 * describe:地址
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Address implements Cloneable {

    private Integer id;
    private String city;

    @Override
    protected Address clone() throws CloneNotSupportedException {
        return (Address) super.clone();
    }
}

从结果可以看出,当我们修改了原型对象的引用属性之后,并没有影响克隆对象,这说明此对象已经实现了深克隆。


3.2 通过构造方法实现深克隆

《Effective Java》 中推荐使用构造器(Copy Constructor)来实现深克隆,如果构造器的参数为基本数据类型或字符串类型则直接赋值,如果是对象类型,则需要重新 new 一个对象,实现代码如下:

/**
 * author:wy
 * describe:收货人2
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Consignee2 {

    public static void main(String[] args) {
        Address2 address = new Address2(102, "北京");
        Consignee2 c1 = new Consignee2(2, "骑士梦", address);
        // 调用构造函数克隆对象
        Consignee2 c2 = new Consignee2(c1.getId(), c1.getName(),
                new Address2(c1.getAddress().getId(), c1.getAddress().getCity()));

        // 修改原型对象
        c1.getAddress().setCity("上海");
        System.out.println(c1);// Consignee2(id=2, name=骑士梦, address=Address2(id=102, city=上海))
        System.out.println(c2);// Consignee2(id=2, name=骑士梦, address=Address2(id=102, city=北京))
    }

    private Integer id;
    private String name;
    private Address2 address;// 包含 Address 对象
}
/**
 * author:wy
 * describe:地址2
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Address2 {

    private Integer id;
    private String city;
}

从结果可以看出,当我们修改了原型对象的引用属性之后,并没有影响克隆对象,这说明此对象已经实现了深克隆。


3.3 通过字节流实现深克隆
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

骑士梦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值