1 对象拷贝的必要性
在java中新建对象实例的最常用的方法是使用new关键字。使用new创建对象轻量级对象时,速度非常快。但是,对于重量级对象,由于对象在构造函数中可能会进行一些复杂且耗时的操作,因此,构造函数的执行时间可能会比较长。这就导致对象的耗时很长。
为此,可以使用object的clone()方法。
2 clone()方法的简介
Object.clone()方法可以绕过对象构造函数,快速复制一个对象实例。由于不需要调用对象构造函数,因此,clone()方法不会受到构造函数性能的影响,可以快速生成一个实例。
protected native Object clone() throws CloneNotSupportedException
解释:
- 首先,这是一个protected方法。protected访问权限的使用说明可以参考:http://zhangjunhd.blog.51cto.com/113473/19287/
- 其次,这是一个native方法,因此效率较高
- 最后,要想某个类的实例能够克隆,那么该类必须实现cloneable接口,不然会抛出CloneNotSupportedException
- 在默认情况下,clone方法生成的实例只是原对象的一个浅拷贝,如果需要深拷贝,则需要重新实现clone方法。下面,我们看一下浅拷贝和深拷贝的区别
3 浅拷贝和深拷贝
浅拷贝是指拷贝对象时仅仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用指向的对象。也就是说,对于对象中的field,不管他是基本类型变量还是引用类型的变量,都直接赋值过去,基本类型变量就是把值拷贝过去,引用类型变量就把引用(即地址)拷贝过去。
深拷贝不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象。
浅拷贝图例:
深拷贝图例:
4 代码实例
4.1 浅拷贝
public class LearnClone {
public static void main(String[] args) throws Exception {
Apple apple=new Apple();
apple.id=1;
apple.color="red";
List<String> list=new ArrayList<String>();
list.add("hello");
apple.list=list;
Apple apple2=apple.clone();
//可以看出,两个对象的地址不同
System.out.println(apple==apple2);//输出false
apple2.id=2;
apple2.list.add("world");
//可以看出源对象和拷贝对象的基本类型变量相互独立。然后,引用类型的变量指向了一个地址
System.out.println(apple);
System.out.println(apple2);
//输出:id:1,color:red,list:[hello, world]
//输出:id:2,color:red,list:[hello, world]
}
}
class Apple implements Cloneable{
int id;
String color;
List<String> list;
@Override
public Apple clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
return (Apple)super.clone();
}
@Override
public String toString() {
// TODO Auto-generated method stub
return "id:"+id+",color:"+color+",list:"+list;
}
}
4.2深拷贝
只需要改写一下浅拷贝的clone函数
class Apple implements Cloneable{
int id;
String color;
List<String> list;
@Override
public Apple clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
Apple apple=(Apple)super.clone();
List<String> list2=new ArrayList<String>();
for (String str : list) {
list2.add(str);
}
apple.list=list2;
return apple;
}
@Override
public String toString() {
// TODO Auto-generated method stub
return "id:"+id+",color:"+color+",list:"+list;
}
}
5 通过序列化实现深拷贝
public class LearnDeepCopy {
public static void main(String[] args) throws Exception{
List<String> list=new ArrayList<String>();
list.add("hello");
Orange orange=new Orange(1, "yellow",list);
Orange copy=null;
//将对象写到流里面
ByteArrayOutputStream byteArrayOutput=new ByteArrayOutputStream();
ObjectOutputStream objectOutput=new ObjectOutputStream(byteArrayOutput);
objectOutput.writeObject(orange);
objectOutput.flush();
//从流里面在读出对象
ByteArrayInputStream byteArrayInput=new ByteArrayInputStream(byteArrayOutput.toByteArray());
ObjectInputStream objectInput=new ObjectInputStream(byteArrayInput);
copy=(Orange)objectInput.readObject();
copy.list.add("world");
//可以发现两个对象的地址不同
System.out.println("orange==copy:"+(copy==orange));
//输出:orange==copy:false
System.out.println(orange);
//输出:id:1,color:yellow,list:[hello]
System.out.println(copy);
//输出:id:1,color:yellow,list:[hello, world]
}
}
class Orange implements Serializable {
private int id;
private String color;
List<String> list;
public Orange(int id,String color,List<String> list) {
// TODO Auto-generated constructor stub
this.id=id;
this.color=color;
this.list=list;
}
@Override
public String toString() {
// TODO Auto-generated method stub
return "id:"+id+",color:"+color+",list:"+list;
}
}
缺点:
- 因为无法序列化transient变量, 使用这种方法将无法拷贝transient变量。
- 性能相比clone来说,较慢。