浅层复制与深层复制的概念
所谓浅层复制(Shallow Copy),是指被复制的对象的所有成员属性都有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅层复制仅仅复制所参考的对象,而不复制它所引用的对象。复制的仅仅是第一层对象。Java默认采用浅层复制,性能好,但隔离性差。
所谓深层复制(Deep Copy),是指被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不是原有的那些被引用的对象。换言之,深层复制需要复制的对象引用的对象都复制一遍。
Java对象的克隆
- 在派生类中实现Cloneable接口
- 利用Object类的clone方法获取对象的一份拷贝
- 在派生类中覆盖继承的clone方法,声明为public
- 在派生类的clone方法中,调用super.clone()
Object.clone()方法解读
public class Object {
protected native Object clone() throws CloneNotSupportedException;
}
由源码我们会发现:
- Object类的clone()方法是一个
native
方法,native
方法的效率一般来说都是远高于Java中的非native
方法。这也解释了为什么要用Object中的clone()方法而不是先new一个类,然后把原始对象中的信息复制到新对象中,虽然这也实现了clone功能。更多有关native关键字的信息参见我的博文Java关键字浅析之native- Object类中的clone()方法被protected修饰符修饰。这也意味着如果要应用clone()方法,必须继承Object类,在Java中所有的类都是缺省继承Object类的,也就不用关心这点了。然后重载clone()方法。还有一点要考虑的是为了让其他类能调用这个clone类的clone()方法,重载之后要把clone()方法的修饰符设置为public。
- Object.clone()方法返回一个Object对象。我们必须进行强制类型转换才能得到我们需要的类型。
Cloneable接口解读
public interface Cloneable {
}
我们会奇怪地发现Cloneable竟然是空的,那么我们到底为什么要实现Cloneable就口呢?实现Cloneable接口的意义何在?其实Cloneable接口仅仅是一个标志,而且这个标志也仅仅是针对Object类中的clone()方法的,如果类没有实现Cloneable接口,调用了Object类的clone()方法(也就是调用了super.clone()方法),那么Object的clone()方法就会抛出CloneNotSupportedException异常。
程序示例:
public class Person implements Cloneable{
private String name;
private int age;
public Person(){}
public Person(String name,int age){
this.name=name;
this.age=age;
}
public Object clone(){
Object o=null;
try {
o=super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return o;
}
public String getName() {return name;}
public void setName(String name) {
this.name = name;
}
public int getAge() {return age;}
public void setAge(int age) {
this.age = age;
}
}
public class PersonTest {
public static void main(String[] args) {
Person p1=new Person("zhangsan",18);
Person p2=(Person)p1.clone();
p2.setName("lis");
p2.setAge(20);
System.out.println("name="
+p1.getName()+",age="+p1.getAge());
//修改p2后,没有对p1产生影响。
}
}
说明:
1. 为什么我们在派生类中覆盖Object类的clone()方法时,一定要调用super.clone()呢?
在运行时刻,Object中的clone()需要识别你要复制的是哪一个对象,然后为此对象分配空间,并进行对象的复制,将原始对象的内容一一拷贝到新对象的存储空间中。
2. 继承自java.lang.Object.clone()方法是浅层复制。证明之:
public class Professor{
private String name;
private int age;
public Professor(){}
public Professor(String name,int age){
this.name=name;
this.age=age;
}
public String getName() {return name;}
public void setName(String name) {
this.name = name;
}
public int getAge() {return age;}
public void setAge(int age) {
this.age = age;
}
}
public class Student implements Cloneable {
private String name;
private int age;
private Professor pro;
public Student(){}
public Student(String name,int age,Professor pro){
this.name=name;
this.age=age;
this.pro=pro;
}
public Object clone(){
Object o=null;
try {
//Object中的clone()识别出你要复制的是哪一个对象。
o=super.clone();
} catch (CloneNotSupportedException e) {
System.out.println(e.toString());
}
return o;
}
public String getName() {return name;}
public void setName(String name) {
this.name = name;
}
public int getAge() {return age;}
public void setAge(int age) {
this.age = age;
public Professor getPro() {return pro;}
public void setPro(Professor pro) {
this.pro = pro;
}
}
public class StudenTest {
public static void main(String[] args) {
Professor p=new Professor("wangwu",50);
Student s1=new Student("zhangsan",18,p);
Student s2=(Student)s1.clone();
s2.getPro().setName("maer");
s2.getPro().setAge(40);
System.out.println("name="+s1.getPro().getName()
+",age="+s1.getPro().getAge());
//name=maer,age=40
}
}
那么我们如何实现深层复制呢?即在修改s2.Professor时不影响s1.Professor?代码改进如下:
public class Student implements Cloneable {
private String name;
private int age;
private Professor pro;
public Student(){}
public Student(String name,int age,Professor pro){
this.name=name;
this.age=age;
this.pro=pro;
}
public Object clone(){
Student o=null;
try {
//Object中的clone()识别出你要复制的是哪一个对象。
o=(Student)super.clone();
} catch (CloneNotSupportedException e) {
System.out.println(e.toString());
}
o.pro = (Professor)pro.clone();
return o;
}
public String getName() {return name;}
public void setName(String name) {
this.name = name;
}
public int getAge() {return age;}
public void setAge(int age) {
this.age = age;
}
public Professor getPro() {return pro;}
public void setPro(Professor pro) {
this.pro = pro;
}
}
public class Professor implements Cloneable{
private String name;
private int age;
public Professor(){}
public Professor(String name,int age){
this.name=name;
this.age=age;
}
public Object clone(){
Object o = null;
try{
o = super.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
return o;
}
public String getName() {return name;}
public void setName(String name) {
this.name = name;
}
public int getAge() {return age;}
public void setAge(int age) {
this.age = age;
}
}
public class StudenTest {
public static void main(String[] args) {
Professor p=new Professor("wangwu",50);
Student s1=new Student("zhangsan",18,p);
Student s2=(Student)s1.clone();
s2.getPro().setName("maer");
s2.getPro().setAge(40);
System.out.println("name="+s1.getPro().getName()
+",age="+s1.getPro().getAge());
//name=wangwu,age=50
}
}
利用串行化来实现深层复制
把对象写到流中的过程就是串行化(Serilization)过程,而把对象从流中读出来是并行化(Deserialization)过程。应当指出的是,写在流中的是对象的一个拷贝,而原来对象仍然存在JVM里面。
在Java语言里深层复制一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流中,再从流中读出来,便可以重建对象。
这样做的前提是对象以及对象内部所有引用到的对象都是可串行化的,否则,就需要仔细考察那些不可串行化的对象是否设成transient,从而将之排除在复制过程之外。代码改进如下:
public class Student implements Serializable {
private String name;
private int age;
Professor pro;
public Student(){}
public Student(String name,int age,Professor pro){
this.name=name;
this.age=age;
this.pro=pro;
}
public Object deepClone() throws IOException, ClassNotFoundException{
//将对象写到流中
ByteArrayOutputStream bo=new ByteArrayOutputStream();
ObjectOutputStream oo=new ObjectOutputStream(bo);
oo.writeObject(this);
//从流中读出来
ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi=new ObjectInputStream(bi);
return oi.readObject();
}
public String getName() {return name;}
public void setName(String name) {
this.name = name;
}
public int getAge() {return age;}
public void setAge(int age) {
this.age = age;
}
public Professor getPro() {return pro;}
public void setPro(Professor pro) {
this.pro = pro;
}
}
public class Professor implements Serializable{
private String name;
private int age;
public Professor(){}
public Professor(String name,int age){
this.name=name;
this.age=age;
}
public String getName() {return name;}
public void setName(String name) {
this.name = name;
}
public int getAge() {return age;}
public void setAge(int age) {
this.age = age;
}
}
public class StudentTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Professor p=new Professor("wangwu",50);
Student s1=new Student("zhangsan",18,p);
Student s2=(Student)s1.deepClone();
s2.getPro().setName("maer");
s2.getPro().setAge(40);
System.out.println("name="+s1.getPro().getName()
+",age="+s1.getPro().getAge());
//name=wangwu,age=50
}
}