Java基础深度总结:Object类-clone方法
凡心所向,素履所往,生如逆旅,一苇以航。
内容
1.clone概述
clone() 是 Object 的 protected 方法,它不是 public,一个类不显式去重写 clone(),其它类就不能直接去调用该类实例的 clone() 方法。
protected native Object clone() throws CloneNotSupportedException;
2.Cloneable接口
如果想要使用clone方法,只覆盖Object的clone方法会抛出CloneNotSupportedException异常。
要想正确使用,该对象的类还要实现一个Cloneable标识接口。
public class CloneExample implements Cloneable {
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
Tips:
- clone() 方法并不是 Cloneable 接口的方法,而是 Object 的一个 protected 方法。
- Cloneable接口的特殊之处在于: Cloneable接口中没有任何方法,它存在的意义就是允许其实现类使用clone方法。
public interface Cloneable {
}
3.clone与new的区别
- new操作符本意实在堆中开辟内存空间。程序执行到new时,会根据new后面的类型来计算分配的内存空间大小,分配完空间以后,调用构造函数填充对象的各个域,然后返回该对象的地址。
- 在调用clone方法时,程序会分配一块与原对象相同大小的内存空间,然后用原对象的各个域填充到新对象相应的各个域中,然后返回新对象的地址。
- 由于native方法通常比非native方法更加高效,所以clone方法效率更高
4.浅拷贝与深拷贝(重点)
当被拷贝的对象中包含引用类型的域时,那么对该域有两种拷贝方式:
- 将原对象中该域的引用值直接拷贝给新对象。
- 根据原对象中该域所引用的对象,创建一个新的、与之相同的对象,然后将该对象的地址赋值给新对象中该域的引用。
前者就是浅拷贝、后者为深拷贝,例如:
可以看出来,在浅拷贝中,虽然两个Person对象是不一样的,但是他们的name却引用的同一个对象,这是由于浅拷贝是将引用类型的引用值直接拷贝到新对象中,那么这两个引用必然会指向同一个对象了;而深拷贝是对引用类型进行了递归clone,创建了另一个对象。
Tip:
- 浅拷贝并没有彻底的将两个对象独立出来
- 彻底的深拷贝需要将引用链上的所有对象都进行递归clone,比如 body对象引用了head,head对象中又引用了face:
5.Object.clone
那么Object中的clone方法是深拷贝还是浅拷贝?答案是:浅拷贝。
public class CloneTest {
public static void main(String[] args) throws CloneNotSupportedException {
Cat cat = new Cat("Tom", 3);
Cat tom = (Cat) cat.clone();
System.out.println(cat == tom); //false
System.out.println(tom.getName() == cat.getName()); // true
tom.setName("Tom1");
System.out.println(cat.getName() +"---" + tom.getName()); // Tom --- Tom1
}
}
class Cat implements Cloneable {
private String name;
private int age;
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
解析:
Cat类中的name是引用类型,如果说原对象和拷贝出来的新对象的name的引用值是相同的,那么就可以说明Object.clone是浅拷贝的,反之代表Object.clone是深拷贝的。从输出结果 System.out.println(tom.getName() == cat.getName()); //true 可以看出来Object.clone确实是浅拷贝。注意: == 比较的是对象的地址。
6.浅拷贝存在的问题
如果对象中包含的域引用了可变的对象,使用Object.clone可能会导致灾难性的后果。比如一个用数组实现的Stack,并且你希望它是可以被clone的。
import java.util.Arrays;
public class ObjectCloneTest{
public static void main(String[] args) throws CloneNotSupportedException {
Stack stack = new Stack();
Stack stack1 = (Stack) stack.clone();
stack1.push(1);
System.out.println(stack.toString());//Stack{data=[1, null, null, null, null, null, null, null, null, null], size=0}
System.out.println(stack1.toString());//Stack{data=[1, null, null, null, null, null, null, null, null, null], size=1}
}
}
class Stack implements Cloneable{
private Object[] data;
private int size;
public Stack(){
data = new Object[10];
size = 0;
}
public void push(Object elem){
if(size == 10) return;
data[size] = elem;
size++;
}
public Object pop(){
if(size == 0) return null;
size --;
return data[size];
}
public int getSize() {
return size;
}
public Object[] getData() {
return data;
}
@Override
public String toString() {
return "Stack{" +
"data=" + Arrays.toString(data) +
", size=" + size +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
当你使用clone出来的对象时,你就会发现很多严重的错误,比如在clone出的对象中添加一个元素,原对象中的size就和数组的实际长度不一致了。这是由于这两个对象的data都引用了同一个数组对象。你应该在data数组中递归地调用clone:
@Override
protected Object clone() throws CloneNotSupportedException {
//return super.clone();
Stack cpyStack = (Stack) super.clone();
cpyStack.data = cpyStack.data.clone();
return cpyStack;
}
Tip:
只有可变对象的引用才会出现上述问题,不可变类(IMMUTABLE CLASS)的引用,如String、基本类型的包装类、BigInteger和BigDecimal等,每次对不可变对象的修改都将产生一个新的不可变对象,因此无论修改clone出的对象的可变对象,还是修改原对象中的可变对象,都会导致可变对象的引用值发生变化,而不是可变对象本身发生变化。
例如:Cat示例中修改了 clone出的tom对象中name的值,这会导致tom对象中的name指向新的字符串对象:“Tom1”,而不会影响cat原对象中name所指向的对象。
7.覆盖clone的规则
由于浅拷贝并不能保证clone出的对象和原对象完全独立,所以在很多时候会导致这样那样的问题,子类覆盖clone一般都是实现深拷贝。
- 首先调用父类super.clone方法(父类必须实现clone方法),这个方法最终会调用Object中的clone方法完成浅拷贝。
- 对类中的引用类型进行单独拷贝。
- 检查clone中是否有不完全拷贝,进行额外的复制。
8.clone的替代方案
使用 clone() 方法来拷贝一个对象即复杂又有风险,它会抛出异常,并且还需要类型转换。Effective Java 书上讲到,最好不要去使用 clone(),可以使用拷贝构造函数或者拷贝工厂来拷贝一个对象。
- 拷贝构造器: public Yum(Yum yum);
- 拷贝工厂: public static Yum newInstance(Yum yum);
- 使用序列化
上一篇: DES加密和解密C++实现
下一篇: golang实现DES加密和解密