介绍

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

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

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

在 Java 语言中要实现克隆则需要实现 Cloneable 接口,并重写 Object 类中的 clone() 方法,实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class CloneExample {
    public static void main(String[] args) throws CloneNotSupportedException {
        // 创建被赋值对象
        People p1 = new People();
        p1.setId(1);
        p1.setName("Java");
        // 克隆 p1 对象
        People p2 = (People) p1.clone();
        // 打印名称
        System.out.println("p2:" + p2.getName());
    }
    static class People implements Cloneable {
        // 属性
        private Integer id;
        private String name;
        /**
         * 重写 clone 方法
         * @throws CloneNotSupportedException
         */
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
        public Integer getId() {
            return id;
        }
        public void setId(Integer id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
    }
}

以上程序执行的结果为:

1
2
p2:Java

考点分析

克隆相关不算太难,和它相关的面试题还有以下这些:

  • 在 java.lang.Object 中对 clone() 方法的约定有哪些?
  • Arrays.copyOf() 是深克隆还是浅克隆?
  • 深克隆的实现方式有几种?
  • Java 中的克隆为什么要设计成,既要实现空接口 Cloneable,还要重写 Object 的 clone() 方法?

知识扩展

clone() 源码分析

要想真正的了解克隆,首先要从它的源码入手,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
 * Creates and returns a copy of this object.  The precise meaning
 * of "copy" may depend on the class of the object. The general
 * intent is that, for any object {@code x}, the expression:
 * <blockquote>
 * <pre>
 * x.clone() != x</pre></blockquote>
 * will be true, and that the expression:
 * <blockquote>
 * <pre>
 * x.clone().getClass() == x.getClass()</pre></blockquote>
 * will be {@code true}, but these are not absolute requirements.
 * While it is typically the case that:
 * <blockquote>
 * <pre>
 * x.clone().equals(x)</pre></blockquote>
 * will be {@code true}, this is not an absolute requirement.
 * <p>
 * By convention, the returned object should be obtained by calling
 * {@code super.clone}.  If a class and all of its superclasses (except
 * {@code Object}) obey this convention, it will be the case that
 * {@code x.clone().getClass() == x.getClass()}.
 * <p>
 * By convention, the object returned by this method should be independent
 * of this object (which is being cloned).  To achieve this independence,
 * it may be necessary to modify one or more fields of the object returned
 * by {@code super.clone} before returning it.  Typically, this means
 * copying any mutable objects that comprise the internal "deep structure"
 * of the object being cloned and replacing the references to these
 * objects with references to the copies.  If a class contains only
 * primitive fields or references to immutable objects, then it is usually
 * the case that no fields in the object returned by {@code super.clone}
 * need to be modified.
 * <p>
 * ......
 */
protected native Object clone() throws CloneNotSupportedException;

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

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

除了注释信息外,我们看 clone() 的实现方法,发现 clone() 是使用 native 修饰的本地方法,因此执行的性能会很高,并且它返回的类型为 Object,因此在调用克隆之后要把对象强转为目标类型才行。

Arrays.copyOf()

如果是数组类型,我们可以直接使用 Arrays.copyOf() 来实现克隆,实现代码如下:

1
2
3
4
5
6
7
People[] o1 = {new People(1, "Java")};
People[] o2 = Arrays.copyOf(o1, o1.length);
// 修改原型对象的第一个元素的值
o1[0].setName("Jdk");
System.out.println("o1:" + o1[0].getName());
System.out.println("o2:" + o2[0].getName());

以上程序的执行结果为:

1
2
3
nums1:[5, 5, 7, 9]
nums2:[5, 5, 7, 9]

从结果可以看出,我们在修改克隆对象的第一个元素之后,原型对象的第一个元素也跟着被修改了,这说明 Arrays.copyOf() 其实是一个浅克隆。

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

深克隆实现方式汇总

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

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

这里给出一个 所有对象都实现克隆方法 的 代码案列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
 * 用户类
 */
public class People {
    private Integer id;
    private String name;
    private Address address; // 包含 Address 引用对象
    // 忽略构造方法、set、get 方法
}
/**
 * 地址类
 */
public class Address {
    private Integer id;
    private String city;
    // 忽略构造方法、set、get 方法
}

可以看出在 People 对象中包含了一个引用对象 Address。

所有对象都实现克隆 这种方式我们需要修改 People 和 Address 类,让它们都实现 Cloneable 的接口,让所有的引用对象都实现克隆,从而实现 People 类的深克隆,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class CloneExample {
    public static void main(String[] args) throws CloneNotSupportedException {
          // 创建被赋值对象
          Address address = new Address(110, "北京");
          People p1 = new People(1, "Java", address);
          // 克隆 p1 对象
          People p2 = p1.clone();
          // 修改原型对象
          p1.getAddress().setCity("西安");
          // 输出 p1 和 p2 地址信息
          System.out.println("p1:" + p1.getAddress().getCity() +
                  " p2:" + p2.getAddress().getCity());
    }
    /**
     * 用户类
     */
    static class People implements Cloneable {
        private Integer id;
        private String name;
        private Address address;
        /**
         * 重写 clone 方法
         * @throws CloneNotSupportedException
         */
        @Override
        protected People clone() throws CloneNotSupportedException {
            People people = (People) super.clone();
            people.setAddress(this.address.clone()); // 引用类型克隆赋值
            return people;
        }
        // 忽略构造方法、set、get 方法
    }
    /**
     * 地址类
     */
    static class Address implements Cloneable {
        private Integer id;
        private String city;
        /**
         * 重写 clone 方法
         * @throws CloneNotSupportedException
         */
        @Override
        protected Address clone() throws CloneNotSupportedException {
            return (Address) super.clone();
        }
        // 忽略构造方法、set、get 方法
    }
}

以上程序的执行结果为:

1
2
p1:西安 p2:北京

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