Java高级特性系列(二):对象的克隆

【引言】面向对象的编程过程中,多多少少会涉及到对象的克隆操作,而就克隆来说呢,不同的人可能有不同的实现方式,那如何实现才是最合适的呢?


为什么需要克隆

  在开发过程中,我们常常会遇到这种情况:已经有了一个对象A,并且A里面的一些属性已经有值了,这时候我们想复制一个A对象到B中,但是再对B进行任何操作都不影响A,这在Java环境中通过=赋值是无法实现的(因为=赋值只是做了栈层面的引用拷贝,堆中的对象实际还是同一个),于是我们需要一个方法可以实现这种需求(当然,你要是不嫌麻烦的话,可以自己new一个对象,然后挨个属性去赋值),克隆的出现就是来解决这个问题的。

什么是对象的克隆?

  克隆(clone),从字面上理解就是复制出一个一模一样的东西(也就是创建出一个新的对象,这时候可能会让人联想起java的new关键字),那么如何复制出来呢?后面的章节会详细的说明;在这里,我们先了解一下Java语言的两种创建对象的方式(new和clone)有什么区别。

使用new操作符创建一个对象

  • new的本意是分配内存,通常new后面会跟一个类型定义(比如:ArrayList)
  • 一旦知道了这个对象的类型,也就知道了初始化内存的大小
  • 于是就去JVM的Heap中申请一块内存区,然后调用构造方法生成对象实例填充到该对象的各个域
  • 至此,完成了一个对象的初始化,对象也就创建完成了
  • 对象创建完成后,就可以将该对象的引用(地址)发布到外部(比如:栈里面的局部变量),通过这个引用即可操作这个对象

使用clone方法复制一个对象

  • clone在第一步是和new相似的,也是分配内存
  • 调用clone方法时,分配的内存和源对象(即调用clone方法的对象)相同
  • 然后再使用原对象中对应的各个域,填充新对象的域,填充完成之后,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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.cc.tools.common;

/**
* Clone方法实验
* @author chenglin
*
*/
public class TestClone {

/**
* Test Main
* @param args
*/
public static void main(String[] args) {

Student student1 = new Student(1, "chenglin");
Student student2 = (Student)student1.clone();

System.out.println(student1.getClass());
System.out.println(student2.getClass());
System.out.println(student1.equals(student2));
System.out.println(student1 == student2);
}
}

/**
* Student类定义
*/
class Student implements Cloneable {
public int id;
public String name;

Student(int id, String name) {
this.id = id;
this.name = name;
}

@Override
public Object clone() {
Student s = null;
try {
s = (Student) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return s;
}
}

---------------------------------------
运行结果:
class com.xunsiya.tools.common.Student
class com.xunsiya.tools.common.Student
false
false

Process finished with exit code 0

Cloneable接口的定义

很明显,该接口是一个里面没有提供任何方法,仅是个标志接口(tagging interface)

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
package java.lang;

/**
* A class implements the <code>Cloneable</code> interface to
* indicate to the {@link java.lang.Object#clone()} method that it
* is legal for that method to make a
* field-for-field copy of instances of that class.
* <p>
* Invoking Object's clone method on an instance that does not implement the
* <code>Cloneable</code> interface results in the exception
* <code>CloneNotSupportedException</code> being thrown.
* <p>
* By convention, classes that implement this interface should override
* <tt>Object.clone</tt> (which is protected) with a public method.
* See {@link java.lang.Object#clone()} for details on overriding this
* method.
* <p>
* Note that this interface does <i>not</i> contain the <tt>clone</tt> method.
* Therefore, it is not possible to clone an object merely by virtue of the
* fact that it implements this interface. Even if the clone method is invoked
* reflectively, there is no guarantee that it will succeed.
*
* @author unascribed
* @see java.lang.CloneNotSupportedException
* @see java.lang.Object#clone()
* @since JDK1.0
*/
public interface Cloneable {
}

clone方法的定义

方法是native定义的,也就是一个java调用非java代码的接口,所以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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/**
* 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>
* The method {@code clone} for class {@code Object} performs a
* specific cloning operation. First, if the class of this object does
* not implement the interface {@code Cloneable}, then a
* {@code CloneNotSupportedException} is thrown. Note that all arrays
* are considered to implement the interface {@code Cloneable} and that
* the return type of the {@code clone} method of an array type {@code T[]}
* is {@code T[]} where T is any reference or primitive type.
* Otherwise, this method creates a new instance of the class of this
* object and initializes all its fields with exactly the contents of
* the corresponding fields of this object, as if by assignment; the
* contents of the fields are not themselves cloned. Thus, this method
* performs a "shallow copy" of this object, not a "deep copy" operation.
* <p>
* The class {@code Object} does not itself implement the interface
* {@code Cloneable}, so calling the {@code clone} method on an object
* whose class is {@code Object} will result in throwing an
* exception at run time.
*
* @return a clone of this instance.
* @throws CloneNotSupportedException if the object's class does not
* support the {@code Cloneable} interface. Subclasses
* that override the {@code clone} method can also
* throw this exception to indicate that an instance cannot
* be cloned.
* @see java.lang.Cloneable
*/
protected native Object clone() throws CloneNotSupportedException;

clone方法的大致逻辑

1
2
3
4
5
6
7
protected Object clone() throws CloneNotSupportedException {  
if (!(this instanceof Cloneable)) {
throw new CloneNotSupportedException("Class doesn't implement Cloneable");
}

return internalClone((Cloneable) this);
}

  如上代码块很清晰的说明了clone的实现逻辑,比如针对上述代码实例删除了implements Cloneable部分的定义,代码也是可以正常编译和运行的,但是运行结果如下:

1
2
3
4
5
6
7
8
9
class com.xunsiya.tools.common.Student
java.lang.CloneNotSupportedException: com.xunsiya.tools.common.Student
at java.lang.Object.clone(Native Method)
at com.xunsiya.tools.common.Student.clone(TestClone.java:42)
at com.xunsiya.tools.common.TestClone.main(TestClone.java:17)
Exception in thread "main" java.lang.NullPointerException
at com.xunsiya.tools.common.TestClone.main(TestClone.java:20)

Process finished with exit code 1

浅克隆和深克隆

浅克隆(shadow clone)

  浅克隆对于要克隆的对象,对于其基本数据类型的属性,复制一份给新产生的对象(如:int,long,float等,复制的是值),对于非基本数据类型的属性,仅仅复制一份引用(reference,可以理解为内存地址)给新产生的对象,即新产生的对象和原始对象中的非基本数据类型的属性都指向的是同一个对象。
  实现浅克隆的步骤:对要克隆的类型本身实现java.lang.Cloneable接口并重写java.lang.Object.clone()方法

深克隆(deep clone)

  在浅克隆的基础上,对于要克隆的对象中的非基本数据类型的属性对应的类,也实现克隆(也就是新建一个对象,新开辟内存区域),这样对于非基本数据类型的属性,复制的不是一份引用,即新产生的对象和原始对象中的非基本数据类型的属性指向的不是同一个对象
  实现深克隆的步骤:对要克隆的类和类中所有非基本数据类型的属性对应的类实现java.lang.Cloneable接口并重写java.lang.Object.clone()方法

总结

  • 重点:待clone的类必须实现Cloneable接口(因为Object并未默认实现Cloneable接口),否则程序编译运行正常但是会报CloneNotSupportedException
  • 重点:实现Cloneable之后,必须要重写public Object clone()方法,通常就是直接调用super.Clone()就可以了(注意:clone方法是从Object继承过来的,因为Cloneable是一个空接口)
  • 重点:Object类中的clone()方法是protected的,重写之后需要把clone()方法设置为public
  • 重点:对象调用clone方法默认返回类型是Object,需要进行强制类型转换
  • 重点:clone前后的两个对象因为在堆中有不同的内存区,所以是不相等的
  • 重点:使用对象序列化和反序列化也可以实现深度克隆(关于序列化的概念会新开一个专题讲述)
------2019 Lin.C ------