1、对象的clone
1.1 实现Cloneable接口
继承Cloneable接口,覆盖public Object clone()
方法。若类中还有其他类的引用,则其他类中也必须覆盖clone方法。
public class Point implements Cloneable
{
int x;
int y;
public Point()
{
}
public Point(int x,int y)
{
this.x=x;
this.y=y;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
@Override
public String toString() {
return "Point [x=" + x + ", y=" + y + "]";
}
@Override
public Object clone() {
Point p = null;
try{
p = (Point)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return p;
}
}
public class Address implements Cloneable {
private String add;
Point center;
public Address()
{
this.add=null;
this.center=null;
}
public Address(String ss,Point center)
{
this.add=ss;
this.center=center;
}
public String getAdd() {
return add;
}
public void setAdd(String add) {
this.add = add;
}
@Override
public Object clone() {
Address addr = null;
try{
addr = (Address)super.clone();
addr.center=(Point)this.center.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return addr;
}
public boolean test(Address o)
{
return this.add==o.add;
}
public boolean testCenter(Address o)
{
return this.center==o.center;
}
@Override
public String toString() {
return "Address [add=" + add + "]";
}
}
public class AppAddress {
public static void main(String[] args) {
Address add=new Address("aaa",new Point(3,3));
Address add2=(Address)add.clone();
System.out.println(add.test(add2)); //true
add2.setAdd("bbb");
System.out.println(add.test(add2)); //false
System.out.println(add.getAdd()); //aaa
add2.center.x=9;
System.out.println(add.testCenter(add2)); //false
}
}
当add2=add.clone()后,add2引用add的地址。当add2改变自己的属性值时,将不再引用add的地址,而是重新指向一块地址,为深拷贝。
clone方式深拷贝小结:
1.如果有一个非原生成员,如自定义对象的成员,那么就需要:
- 该成员实现Cloneable接口并覆盖clone()方法,不要忘记提升为public可见。
- 同时,修改被复制类的clone()方法,增加成员的克隆逻辑。
2. 如果被复制对象不是直接继承Object,中间还有其它继承层次,每一层super类都需要实现Cloneable接口并覆盖clone()方法。
与对象成员不同,继承关系中的clone不需要被复制类的clone()做多余的工作。
一句话来说,如果实现完整的深拷贝,需要被复制对象的继承链、引用链上的每一个对象都实现克隆机制。
前面的实例还可以接受,如果有N个对象成员,有M层继承关系,就会很麻烦。
1.2 BeanUtils
BeanUtils是一个工具类,类中提供了cloneBean(Object object)方法。
自己实现BeanUtils(反射机制):
public class BeanUtils
{
public static Object copyObject(Object obj)
{
if(obj==null) return null;
Class clz=obj.getClass();
Object temp=null;
try{
temp=clz.newInstance();
Field[] fields=clz.getDeclaredFields();
for (Field field : fields)
{
field.setAccessible(true);
Object value=field.get(obj);
if(check(field.getType()))
{
field.set(temp, value);
}
else
{
Object t=copyObject(value);
field.set(temp, t);
}
}
}catch(Exception e)
{
e.printStackTrace();
}
return temp;
}
private static boolean check(Class clz)
{
if(clz==Integer.class||clz==Integer.TYPE) return true;
if(clz==Double.class||clz==Double.TYPE) return true;
if(clz==Float.class||clz==Float.TYPE) return true;
if(clz==Boolean.class||clz==Boolean.TYPE) return true;
if(clz==Long.class||clz==Long.TYPE) return true;
if(clz==String.class) return true;
return false;
}
}
public static void main(String[] args) throws IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException {
// TODO Auto-generated method stub
Address add=new Address("aaa",new Point(3,3));
Address add2=(Address)BeanUtils.copyObject(add);
System.out.println(add.testCenter(add2)); //false
Address add3 =(Address)org.apache.commons.beanutils.BeanUtils.cloneBean(add);
System.out.println(add.test(add3)); //false
System.out.println(add.testCenter(add3)); //false
}
1.3 序列化
public class Person implements Serializable {
private String name;
private Integer age;
private Address address;
public Person deepClone() {
Person p2=null;
Person p1=this;
PipedOutputStream out=new PipedOutputStream();
PipedInputStream in=new PipedInputStream();
try {
in.connect(out);
} catch (IOException e) {
e.printStackTrace();
}
try(ObjectOutputStream bo=new ObjectOutputStream(out);
ObjectInputStream bi=new ObjectInputStream(in);) {
bo.writeObject(p1);
p2=(Person) bi.readObject();
} catch (Exception e) {
e.printStackTrace();
}
return p2;
}
}
clone机制不是强类型的限制,比如实现了Cloneable并没有强制继承链上的对象也实现;也没有强制要求覆盖clone()方法。因此编码过程中比较容易忽略其中一个环节,对于复杂的项目排查就是困难了。
要寻找可靠的,简单的方法,序列化就是一种途径。
- 被复制对象的继承链、引用链上的每一个对象都实现java.io.Serializable接口。这个比较简单,不需要实现任何方法,serialVersionID的要求不强制,对深拷贝来说没毛病。
- 实现自己的deepClone方法,将this写入流,再读出来。俗称:冷冻-解冻。