Java-深克隆&浅克隆

浅克隆(Shadow Clone)

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

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

深克隆(Deep Clone)

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

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 等。
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();
    }
}

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

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. 通过字节流实现深克隆
上一篇:Java Web学习 Day06 JSP标签、JSTL标签、EL表达式


下一篇:框架开发之Java注解的妙用