在Java中赋值是很常用的
//原始类型 int a = 1; int b = a; //引用类型 String[] weekdays = new String[5]; String[] gongzuori = weekdays; //仅拷贝引用 /* *在上述代码中如果是原始数据类型,赋值传递的为真实的值;如果是引用类型,赋值传递的是对象的引用,而不是对象 */
Clone
在Java中,clone是将已有的对象在内存中赋值到另一个与之相同的对象的过程。Java中的克隆为逐域复制。
在Java中药支持clone方法,需要首先实现Cloneable接口,此接口不包含任何方法,仅仅是一个标记接口。
需要注意的是:如果想要支持clone,就需要实现Cloneable接口,如果没有实现Cloneable接口调用clone()方法,会抛出CloneNotSupportedException异常。
然后是重写clone方法,并修改成public访问级别:
static class CloneableImp implements Cloneable { public int count; public Child child; @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } }
//调用clone方法赋值对象 CloneableImp imp1 = new CloneableImp(); imp1.child = new Child("Andy"); try { Object obj = imp1.clone(); CloneableImp imp2 = (CloneableImp)obj; System.out.println("main imp2.child.name=" + imp2.child.name); } catch (CloneNotSupportedException e) { e.printStackTrace(); }
浅拷贝
上面的代码实现的clone实际上是属于浅拷贝(Shallow Copy)。
关于浅拷贝,你该了解的
使用默认的clone方法
对于原始数据域进行值拷贝
对于引用类型仅拷贝引用
执行快,效率高
不能做到数据的100%分离。
如果一个对象只包含原始数据域或者不可变对象域,推荐使用浅拷贝。
关于无法做到数据分离,我们可以使用这段代码验证
CloneableImp imp1 = new CloneableImp(); imp1.child = new Child("Andy"); try { Object obj = imp1.clone(); CloneableImp imp2 = (CloneableImp)obj; imp2.child.name = "Bob"; System.out.println("main imp1.child.name=" + imp1.child.name); } catch (CloneNotSupportedException e) { e.printStackTrace(); }
上述代码我们使用了imp1的clone方法克隆出imp2,然后修改 imp2.child.name 为 Bob,然后打印imp1.child.name 得到的结果是
main imp1.child.name=Bob //原因是浅拷贝并没有做到数据的100%分离,imp1和imp2共享同一个Child对象,所以一个修改会影响到另一个。
深拷贝
深拷贝可以解决数据100%分离的问题。只需要对上面代码进行一些修改即可。
1、Child实现Cloneable接口。
public class Child implements Cloneable{ public String name; public Child(String name) { this.name = name; } @Override public String toString() { return "Child [name=" + name + "]"; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
2.重写clone方法,调用数据域的clone方法。
static class CloneableImp implements Cloneable { public int count; public Child child; @Override public Object clone() throws CloneNotSupportedException { CloneableImp obj = (CloneableImp)super.clone(); obj.child = (Child) child.clone(); return obj; } }
当我们再次修改imp2.child.name就不会影响到imp1.child.name的值了,因为imp1和imp2各自拥有自己的child对象,因为做到了数据的100%隔离。
关于深拷贝的一些特点
需要重写clone方法,不仅仅只调用父类的方法,还需调用属性的clone方法
做到了原对象与克隆对象之间100%数据分离
如果是对象存在引用类型的属性,建议使用深拷贝
深拷贝比浅拷贝要更加耗时,效率更低
Copy constructors
使用复制构造器也可以实现对象的拷贝。
复制构造器也是构造器的一种
只接受一个参数,参数类型为当前的类
目的是生成一个与参数相同的新对象
复制构造器相比clone方法的优势是简单,易于实现。
一段使用了复制构造器的代码示例
public class Car { Wheel wheel; String manufacturer; public Car(Wheel wheel, String manufacturer) { this.wheel = wheel; this.manufacturer = manufacturer; } //copy constructor public Car(Car car) { this(car.wheel, car.manufacturer); } public static class Wheel { String brand; } } //注意,上面的代码实现为浅拷贝,如果想要实现深拷贝,参考如下代码
public Car(Car car) { Wheel wheel = new Wheel(); wheel.brand = car.wheel.brand; this.wheel = wheel; this.manufacturer = car.manufacturer; }
为了更加便捷,我们还可以为上述类增加一个静态的方法
public static Car newInstance(Car car) { return new Car(car); }
使用Serializable实现深拷贝
其实,使用序列化也可以实现对象的深拷贝。简略代码如下
public class DeepCopyExample implements Serializable{ private static final long serialVersionUID = 6098694917984051357L; public Child child; public DeepCopyExample copy() { DeepCopyExample copy = null; try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(this); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); copy = (DeepCopyExample) ois.readObject(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return copy; } }
其中,Child必须实现Serializable接口
public class Child implements Serializable{ private static final long serialVersionUID = 6832122780722711261L; public String name = ""; public Child(String name) { this.name = name; } @Override public String toString() { return "Child [name=" + name + "]"; } }
package abc; class Address implements Cloneable { private String add; 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(); }catch(CloneNotSupportedException e) { e.printStackTrace(); } return addr; } } class Student implements Cloneable{ private int number; private Address addr; public Address getAddr() { return addr; } public void setAddr(Address addr) { this.addr = addr; } public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } @Override public Object clone() { Student stu = null; try{ stu = (Student)super.clone(); //浅复制 调用父类clone()方法 复制学生类对象 }catch(CloneNotSupportedException e) { e.printStackTrace(); } stu.addr = (Address)addr.clone(); //深度复制 复制地址值 Address类重写了clone()方法 所以当调用此处clone()方法时,会深度调用Address类对象 return stu; } } public class Test { public static void main(String args[]) { Address addr = new Address(); addr.setAdd("杭州市"); Student stu1 = new Student(); stu1.setNumber(123); stu1.setAddr(addr); Student stu2 = (Student)stu1.clone(); System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd()); System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd()); addr.setAdd("西湖区"); System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd()); System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd()); } }
class AddressTest implements Cloneable{ String add; public AddressTest(String add){ this.add = add; } //重写clone()方法 public Object clone(){ AddressTest obj = null; try { obj = (AddressTest)super.clone(); } catch (CloneNotSupportedException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } return obj; } @Override public String toString() { // TODO 自动生成的方法存根 return add; } } class StudentTest implements Cloneable{ String name; AddressTest add; public StudentTest(String name, AddressTest add) { this.name = name; this.add = add; } public Object clone(){ StudentTest stu = null; try { stu = (StudentTest)super.clone();//浅拷贝 stu.add = (AddressTest)add.clone();//深度拷贝,add所在类中的clone()方法是重写过的 } catch (CloneNotSupportedException e) { e.printStackTrace(); } return stu; } @Override public String toString() { return name + "------" + add; } } public class CloneDemo { public static void main(String[] args) { AddressTest a1 = new AddressTest("杭州"); AddressTest a2 = new AddressTest("北京"); StudentTest s1 = new StudentTest("zed1",a2); StudentTest s2 = (StudentTest)s1.clone(); s1.add = a1; System.out.println("s1: " + s1.toString()); System.out.println("s2: " + s2.toString()); } }
class AddressTest /*implements Cloneable*/{ String add; public AddressTest(String add){ this.add = add; } //重写clone()方法 // public Object clone(){ // AddressTest obj = null; // try { // obj = (AddressTest)super.clone(); // } catch (CloneNotSupportedException e) { // // TODO 自动生成的 catch 块 // e.printStackTrace(); // } // return obj; // } @Override public String toString() { // TODO 自动生成的方法存根 return add; } } class Name{ int count; String name; public Name(String name){ this.name = name; } @Override public String toString() { // TODO 自动生成的方法存根 return name; } } class StudentTest implements Cloneable{ Name name; AddressTest add; public StudentTest(Name name){ this.name = name; } public StudentTest(Name name, AddressTest add) { this.name = name; this.add = add; } public Object clone(){ StudentTest stu = null; try { stu = (StudentTest)super.clone();//浅拷贝 //stu.add = (AddressTest)add.clone();//深度拷贝,add所在类中的clone()方法是重写过的 } catch (CloneNotSupportedException e) { e.printStackTrace(); } return stu; } @Override public String toString() { return name + "------" + add; } } public class CloneDemo { public static void main(String[] args) { AddressTest a1 = new AddressTest("杭州"); AddressTest a2 = new AddressTest("北京"); Name n1 = new Name("zed1"); StudentTest s1 = new StudentTest(n1,a1); //s1.name = new Name("zed2"); StudentTest s2 = s1; //都会输出a2的值,所以是浅拷贝,a2并没有深度拷贝 s2.add = a2; System.out.println("s1: " + s1.toString()); System.out.println("s2: " + s2.toString()); } }
深拷贝和浅拷贝都是对象拷贝,不同的是对象类中存在其他类对象,他们都是拷贝这个对象,不同的是深拷贝拷贝的是这个对象,浅拷贝拷贝的是这个对象的引用地址。也就是浅拷贝如果更改类中引用的对象,那个地址的对象就会改变(浅拷贝的两个对象中的对象引用指向同一地址,相当于共享对象),而深拷贝就不会。所以深拷贝会在拷贝的时候处理对象中的引用对象。