Java中对象的克隆
在Java中有时需要通过一个对象object1的值来构造另一个对象object2,同时希望在使用oject2的时候不影响object1.这时候就需要对象的克隆技术,这里介绍我知道的两种对象的深拷贝的方法。
浅拷贝与深拷贝
所谓的浅拷贝就是,把一个对象的引用给了另一个对象,在堆中实际上还是同一个对象,只不过在栈中给他起了两个另一个名字。
比如有一个object1,通过 object = object1;对象object得到了对象object1的引用,但是对object的修改将会影响object1的值。
深拷贝就是从一个object1得到另一个与其值完全一样的object,但对object的使用不会影响原来的object1的值。
实现Cloneable接口
在java中有一个Cloneable接口
要让一个对象进行克隆,其实就是两个步骤:
1.让该类实现java.lang.Cloneable接口; 2.重写(override)Object类的clone()方法。
class Person implements Cloneable{
String name;
int age;
public Person(String name,int age){
this.name = name;
this.age = age;
}
@Override
public Person clone() { //重写clone()方法
Person p = null;
try {
p = (Person)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return p;
}
@Override
public int hashCode(){
return this.name.hashCode() + age;
}
@Override
public boolean equals(Object obj) {
Person p = (Person)obj;
if(this.name.equals(p.name)&& this.age == p.age)
return true;
return false;
}
public void print(){
System.out.println("name\t" + this.name);
System.out.println("age\t" + this.age);
System.out.println();
}
}
在main方法中,通过如下方式调用
public class CloneTest {
public static void main(String[] args) {
Person p1 = new Person("tom", 22);
Person p2 = p1;
Person p3 = p1.clone();
p1.age = 23;
p1.print();
p2.print();
p3.print();
}
}
打印结果如下
name tom
age 23
name tom
age 23
name tom
age 22
p2是通过p1直接 p2 = p1 直接赋值的,这样的话只是把p1的引用给了p2。其实就是p1和p2指向同一个对象。而p3是通过p1.clone()方法得到的,得到的是p1的深拷贝。因此修改p1的age时候会影响p2的age,而不会影响p3的age。
好。如果此时以为已经回了对象的克隆那就大错特错了。因为string和int类型在拷贝的时候自动的实现深拷贝,而如果对象中的私有域是一个引用,比如一个数组、另一个对象、一个list等除了基本类型的域,那么克隆的依然是一个引用。
比如
class Phone {
String grand;
Phone(String grand){
this.grand = grand;
}
}
class Person implements Cloneable{
String name;
int age;
ArrayList<String> list ;
String [] str = new String[2];
Phone phone = null;
public Person(String name,int age,List<String> list,String string){
this.name = name;
this.age = age;
this.list = new ArrayList<String>(list);
this.phone = new Phone(string);
str [0] = "str1";
str [1] = "str2";
}
@SuppressWarnings("unchecked")
@Override
public Person clone() {
Person p = null;
try {
p = (Person)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return p;
}
@Override
public int hashCode(){
return this.name.hashCode() + age;
}
public void print(){
System.out.println("name\t" + this.name);
System.out.println("age\t" + this.age);
System.out.println("list\t" + this.list);
System.out.println("str\t"+str[0] + " " + str[1]);
System.out.println("phone\t"+this.phone.grand);
System.out.println();
}
}
main方法中调用如下:
public class CloneTest {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("test1");
list.add("test2");
Person p1 = new Person("tom", 22,list,"NOKIA");
Person p2 = p1;
Person p3 = p1.clone();
p1.age = 23; //修改p1.age
p1.name = "jack"; //修改p1.name
p1.list.set(0, "test"); //修改p1.list
p1.str[0] = "str"; //修改p1.str数组
p1.phone.grand = "APPLE"; //修改p1中phone对象的值
p1.print();
p2.print();
p3.print();
}
}
打印结果如下:
name jack
age 23
list [test, test2]
str str str2
phone APPLE
name jack
age 23
list [test, test2]
str str str2
phone APPLE
name tom
age 22
list [test, test2]
str str str2
phone APPLE
你会发现引用对象并没有实现克隆,而如果想真正实现克隆,必须在Person类中这样定义clone方法。
@SuppressWarnings("unchecked")
@Override
public Person clone() {
Person p = null;
try {
p = (Person)super.clone();
p.list = (ArrayList<String>) this.list.clone();
p.str = this.str.clone();
p.phone = this.phone.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return p;
}
同时对于Phone类,也需要实现Cloneable接口并重写clone方法。
class Phone implements Cloneable{
String grand;
Phone(String grand){
this.grand = grand;
}
public Phone clone() {
Phone p = null;
try {
p = (Phone)super.clone();
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return p;
}
}
重新运行程序,输出如下:
name jack
age 23
list [test, test2]
str str str2
phone APPLE
name jack
age 23
list [test, test2]
str str str2
phone APPLE
name tom
age 22
list [test1, test2]
str str1 str2
phone NOKIA
发现此时对p1的修改将不会影响到p3只会影响到p2。
至于深克隆的层次,由具体的需求决定,也有“N层克隆”一说。所有的基本(primitive)类型数据,无论是浅克隆还是深克隆,都会进行原值克隆。毕竟他们都不是对象,不是存储在堆中。注意:基本数据类型并不包括他们对应的包装类。
缺点:如果有N多个持有的对象,那就要写N多的方法,突然改变了类的结构,还要重新修改clone()方法。
通过序列化实现深度克隆
public Person deepClone() {
ByteArrayOutputStream byteOut = null;
ObjectOutputStream objOut = null;
ByteArrayInputStream byteIn = null;
ObjectInputStream objIn = null;
try {
byteOut = new ByteArrayOutputStream();
objOut = new ObjectOutputStream(byteOut);
objOut.writeObject(this);
byteIn = new ByteArrayInputStream(byteOut.toByteArray());
objIn = new ObjectInputStream(byteIn);
return (Person) objIn.readObject();
} catch (IOException e) {
throw new RuntimeException("Clone Object failed in IO.",e);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Class not found.",e);
} finally{
try{
byteIn = null;
byteOut = null;
if(objOut != null) objOut.close();
if(objIn != null) objIn.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
通过序列化能轻松构造一个对象的深克隆,不过也需要注意一些问题。比如曾经序列化了一个对象,可由于某种原因,该类做了一点点改动,然后重新被编译,那么这时反序列化刚才的对象,将会出现异常。 你可以通过添加serialVersionUID属性来解决这个问题。如果你的类是个单例(Singleton)类,是否允许用户通过序列化机制复制该类,如果不允许你需要谨慎对待该类的实现。
总结
目前可有如上两种方法,得到一个对象的深克隆。比重新new一个,并一个个赋值快多了。因为上述方法都是直接操作内存中的二进制流,所以非常迅速。
下面开始学习多线程。
最新评论