我们都知道,java提供了Cloneable接口,其意思就是对象的拷贝,我们用到这功能的时候,需实现这个接口!
那我们为什么要用它?用它能给我们带来什么好处呢?
因为拷贝是在内存中进行的,所以,比直接new对象要快,特别是在大对象的生存上(有别于数据库中的clob,特指属性比较多,字段比较复杂的对象),但是对象的拷贝也会带给我们一些迷惑,稍有不慎便会陷入这个坑中。
列子1:
public class TestClone {
public static void main(String[] args) {
Person father=new Person("父亲");
Person subPer=new Person("儿子",father);
//儿子二是通过subPer拷贝过来的
Person subPer1=subPer.clone();
subPer1.setName("儿子1");
System.out.println(subPer.getName()+"的父亲是"+subPer.getFather().getName());
System.out.println(subPer1.getName()+"的父亲是"+subPer.getFather().getName());
}
}
public class Person implements Cloneable {
//姓名
private String name="";
//父亲
private Person father=null;
public Person(String name){
this.name=name;
}
public Person(String name,Person father){
this.name=name;
this.father=father;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person getFather() {
return father;
}
public void setFather(Person father) {
this.father = father;
}
@Override
protected Person clone() {
Person p=null;
try{
p=(Person) super.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
return p;
}
}
这里模拟了一个父亲有两个儿子这么一个场景,大儿子和小儿子同种,所以小儿子的对象是通过拷贝大儿子的对象来完成的,这十分正确,没什么问题的啊!但是呢?软件的开发修改时无穷尽的,就比如有那么一天,老爸突然心血来潮,突然想给大儿子找个干爹
Person father=new Person("父亲");
Person subPer=new Person("儿子",father);
//儿子1二是通过儿子拷贝过来的
Person subPer1=subPer.clone();
subPer1.setName("儿子1");
subPer.getFather().setName("大儿子的个干爹");
System.out.println(subPer.getName()+"的父亲是"+subPer.getFather().getName());
System.out.println(subPer1.getName()+"的父亲是"+subPer.getFather().getName());
其结果是
Person father=new Person("父亲");
Person subPer=new Person("儿子",father);
//儿子1二是通过儿子拷贝过来的
Person subPer1=subPer.clone();
subPer1.setName("儿子1");
subPer.getFather().setName("大儿子的个干爹");
System.out.println(subPer.getName()+"的父亲是"+subPer.getFather().getName());
System.out.println(subPer1.getName()+"的父亲是"+subPer.getFather().getName());
其结果是:
儿子的父亲是大儿子的个干爹
儿子1的父亲是大儿子的个干爹
两儿子的老爸都变了,这个老爹从人间蒸发,妈妈啊!
出现这种现象的原因是什么呢?
我们都知道,java中所有的类都继承Object,Object提供了一个默认的拷贝方法,即super.clone(),但是,该方法是有缺陷的,它提供的是一种浅拷贝,它不把对象的所有属性都拷贝一份,它支持基本数据类型 比如int,float,double,如果变量是一个实例对象,则拷贝地址引用,也就是说此时新拷贝出来的对象与原有的对象共享该实例变量,不受访问权限的限制。我的妈妈啊!这不是违背了java 中private 只能在一个类中使用的原则么,这让java 的private 情何以堪!!!!
也许你也发现了,String不也是引用类型的么?不也是能正常拷贝么,呵呵,这就得说说String 这个玩意,这个玩意比较特殊,特殊在什么地方呢?其实clone 的时候也是拷贝的地址,但是呢,修改的时候会从字符串池中重新生成新的字符串,原有的字符串保存不变,其实这么一说也就明白了 原来儿子一是通过大儿子产生的,我的乖乖!!那怎么修改呢?
protected Person clone() {
Person p=null;
try{
p=(Person) super.clone();
p.setFather(new Person(p.getFather().getName()));
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
return p;
}
这就是对引用类型的拷贝,聪明的你或许已经发现,其实对引用的拷贝是在什么地方修改的,那我们来看看集合的拷贝!
列2:对集合的拷贝
public class Person implements Cloneable{
// 姓名
private String name;
// 年龄
private int age;
// 性别
private String sex;
//朋友
private List
public List
return friends;
}
public void setFriends(List
this.friends = friends;
}
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 String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Person clone() {
try {
Person person = (Person)super.clone();
List
for(String friend : this.getFriends()) {
newfriends.add(friend);
}
person.setFriends(newfriends);
return person;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
ok,我们已经知道了拷贝的使用,但是呢?你想过没,如果项目中有大量的对象是通过拷贝来生存的,那我们该如何处理呢?一种选择方式是通过序列化方式来处理,何为序列化?就是对象可以通过介质传输,列如内存,然后通过反序列化可以得到原始的对象,
public class CloneUtils {
public CloneUtils() {
throw new Error("工具类不能创建对象!");
}
@SuppressWarnings("unchecked")
public static
// T 拷贝产生的对象
T cloneObj = null;
try {
// 读取对象字节流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
oos.close();
// 分配内存空间,写入原始对象,生存新对象
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
cloneObj = (T) ois.readObject();
ois.close();
} catch (Exception e) {
}
return cloneObj;
}
}