Java技巧------Cloneable接口与clone方法

描述

Cloneable只起到一个描述标记的作用,这个接口没有定义任何方法或是包含其他内容。我们可以看一下源码定义。

1
2
3
4
5
6
7
8
9
package java.lang;
/**
* @author unascribed
* @see java.lang.CloneNotSupportedException
* @see java.lang.Object#clone()
* @since JDK1.0
*/
public interface Cloneable {
}

真的是啥都没有。。。。。。那这样一个接口有什么作用呢?
源码的描述是:一个类实现Cloneable接口,表明java.lang.Object中的clone()函数是合法的,即这个类可以使用clone()方法。
我们再看看位于Object类中的clone()这个函数方法。

1
protected native Object clone() throws CloneNotSupportedException;

值得一提,对其的描述中有很多的细节,我们简单的来看看。
首先是对这个方法的介绍,它是一个native标记的方法,它返回的是对该对象Object的拷贝。可能会出现CloneNotSupportedException异常。

  1. x.clone() != x //拷贝返回的对象与原来的对象不同,是新的对象而不是原有的对象的引用。
  2. x.clone().getClass() == x.getClass() //但是两个对象的从属的类相同。
  3. x.clone().equals(x) //拷贝返回的对象与原来的对象使用equals方法返回为true(一般情况)

于是一切就很好解释了,当一个类要实现clone()方法时,必须要实现Cloneable接口,否则会抛出CloneNotSupportedException异常,而Cloneable并不含有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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
public class A implements Cloneable {
private int param;
private B b;

public A(int param, B b) {
this.param = param;
this.b = b;
}

public void setParam(int param) {
this.param = param;
}

public void setB(B b) {
this.b = b;
}

public int getParam() {
return param;
}

public B getB() {
return b;
}
//使用super.clone()方法实现拷贝
//递归进行实现拷贝
@Override
protected Object clone() throws CloneNotSupportedException {
A t = (A) super.clone();
t.setB((B) b.clone());
return t;
}
}

public class B implements Cloneable{
private int param;
private C c;

public C getC() {
return c;
}

public int getParam() {
return param;
}

public B(int param, C c){
this.param=param;
this.c=c;
}

public void setParam(int param) {
this.param = param;
}

public void setC(C c) {
this.c = c;
}

@Override
protected Object clone() throws CloneNotSupportedException {
B t= (B) super.clone();
return t;
}
}

public class C {
private int param;

public C(int param) {
this.param = param;
}

public void setParam(int param) {
this.param = param;
}

public int getParam() {
return param;
}
}

拷贝

先看看值传递与引用传递
值传递:直接传递数值,将要传入的值在内存中复制一份传到函数中,在函数中的操作不会影响原先实参的值,但是数值的复制效率很低。
引用传递:将相应的地址值进行传递,形参和实参的值相同,指向同一处内存地址,在函数中进行的修改将影响到原先的数据。

浅拷贝

对基本数据类型的数据变量采取值传递的方式,直接将数值复制一份传入,但对于引用类的数据则是将其进行应用传递。之后当我们对被拷贝的数据进行修改时,使用值传递的拷贝数据将不被影响,而使用引用传递的数据将被影响而发生改变。

简单实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) throws CloneNotSupportedException {
C c=new C(10);
B b=new B(11,c);
B s= (B) b.clone();
System.out.println(b.getParam()+" "+b.getC().getParam());
System.out.println(s.getParam()+" "+s.getC().getParam());
c.setParam(20);
b.setParam(21);
System.out.println(b.getParam()+" "+b.getC().getParam());
System.out.println(s.getParam()+" "+s.getC().getParam());
}
//结果
//11 10
//11 10
//21 20
//11 20

深拷贝

与浅拷贝相似,只是在对于引用类型的数据处理时,不使用引用传递,而是将引用的数据在内存中另开辟空间,复制存入,如果存在引用数据的递归调用则对其进行递归复制处理引用类型的数据。对于在类中嵌入其他类进行引用的数据,相当于对这些类的引用对象再进行浅拷贝,并递归下去直至到,没有再引用其他类为止。

简单实现

先将B类改一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class B implements Cloneable{
private int param;

public int getParam() {
return param;
}

public B(int param){
this.param=param;
}

public void setParam(int param) {
this.param = param;
}

@Override
protected Object clone() throws CloneNotSupportedException {
B t= (B) super.clone();
return t;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) throws CloneNotSupportedException {
B b=new B(10);
A a=new A(11,b);
A s= (A) a.clone();
System.out.println(a.getB().getParam()+" "+a.getParam());
System.out.println(s.getB().getParam()+" "+s.getParam());
b.setParam(20);
a.setParam(21);
System.out.println(a.getB().getParam()+" "+b.getParam());
System.out.println(s.getB().getParam()+" "+s.getParam());
}
//结果
//10 11
//10 11
//20 20
//10 11

小结

clone方法是一个native修饰的方法,其实现是在jvm中实现的,由于是native实现,所以效率上有优势,Cloneable接口只是一个标记,在具体的jvm中实现中会具体的处理判断是否实现Cloneable接口。