概述
Java复制分为三类:直接赋值、浅拷贝和深拷贝。
Java复制是基于Object的clone()方法。
/*
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 x, the expression:
1) x.clone() != x will be true
2) x.clone().getClass() == x.getClass() will be true, but these are not absolute requirements.
3) x.clone().equals(x) will be true, this is not an absolute requirement.
*/
protected native Object clone() throws CloneNotSupportedException;
由此可见,clone()是一个native方法(非Java代码实现,供Java调用),因为Java程序运行在JVM中,想要访问比较底层的与操作系统相关的,只能由靠近操作系统的语言来实现。
- 第一次声明保证克隆对象就有单独的内存地址分配。
- 第二次声明表明,原始和克隆对象应该具有相同的类类型,但它并非强制额。
- 第三次声明表明,原始和克隆对象应该是平等的equals()方法使用,但它不是强制性的。
每个类直接或间接的父类都是Object,因此它们都含有clone()方法,但该方法是protected,所以不能在类外进行访问。想对一个对象复制,需要对clone对象进行覆盖。
通过clone方法赋值的对象跟原来的对象时同时独立存在的。
直接赋值
在 Java 中,A a1 = a2,我们需要理解的是这实际上复制的是引用,也就是说 a1 和 a2 指向的是同一个对象。因此,当 a1 变化的时候,a2 里面的成员变量也会跟着变化。
浅拷贝(ShollowClone)
- 复制引用,但不复制引用指向的对象。
- 在浅复制中,创建一个对象,如果原型对象的成员变量是值类型,则复制一份给克隆对象。如果原型对象中的成员变量是引用类型,则将引用对象的地址复制给克隆对象,而不复制引用对象,即原型对象和克隆对象的引用类型成员变量指向同一对象。
-
图解
浅拷贝.png - 实现
- 被复制的类需实现Cloneable接口(否则调用clone()方法时会报出CloneNotSupportedException异常)。
- 覆盖clone()方法,并将修饰符改为public。方法中调用super.clone()【native方法】得到原型对象。
Java语言提供的Cloneable接口和Serializable接口的代码非常简单,只是空接口,也称标识接口,没有任何方法的定义,其作用是告诉JRE这些接口,是否具有这些功能,比如是否支持复制,是否支持序列化等。
- 例子
public Class Orders implements Cloneable {
private String oid;
private String ordername;
private User user;
...
public Object clone(){
try {
return (Orders)super.clone();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
深拷贝(DeepClone)
- 复制对象及其引用的对象
- 在深拷贝中,无论值类型或者引用类型成员变量都将拷贝一份给克隆对象。
-
图解
深拷贝.png - 实现
- 方法一:实现Cloneable接口,并覆盖Object的clone()方法。
public Class User implements Cloneable {
private String userid;
private String username;
...
public Object clone(){
try {
return (User )super.clone();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
public Class Orders implements Cloneable {
private String oid;
private String ordername;
private User user;
...
public Object clone(){
Orders orders = null;
try {
orders = (Orders)super.clone();
} catch (Exception e) {
e.printStackTrace();
}
orders.user = (User)user.clone();
return orders;
}
}
- 方法二:通过序列化(解决多层复制问题)
如果引用类型中还包含着很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone就会很麻烦,可用序列化来实现深度复制。
序列化就是将对象写到流的过程,写到流中的对象时原有对象的拷贝,而原对象仍在内存中。通过序列化实现的拷贝,不仅可以复制其对象本身,还可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深拷贝。需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。
public class Outer implements Serializable{
private static final long serialVersionUID = 369285298572941L; //最好是显式声明ID
public Inner inner;
//Discription:[深度复制方法,需要对象及对象所有的对象属性都实现序列化]
public Outer myclone() {
Outer outer = null;
try { // 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
// 将流序列化成对象
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
outer = (Outer) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return outer;
}
}
public class Inner implements Serializable{
private static final long serialVersionUID = 872390113109L; //最好是显式声明ID
public String name = "";
public Inner(String name) {
this.name = name;
}
@Override
public String toString() {
return "Inner的name值为:" + name;
}
}
总结
- 实现Cloneable接口并重写Object类中的clone()方法;
- 实现Serializable接口,通过对象的序列化和反序列化实现拷贝,可以实现真正的深度拷贝。
基于序列化和反序列化实现的拷贝不仅是深度拷贝,更重要的是通过泛型限定,可以检查出要拷贝的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种方案明显优于使用Object类的clone方法拷贝对象。让问题在编译的时候暴露出来总是优于把问题留到运行时。
网友评论