如果你想复制一个简单变量。很简单:
int x = 7; int y = x;
不仅仅是int类型,其它七种原始数据类型(boolean,char,byte,short,float,double.long)同样适用于该类情况。
但是如果你复制的是一个对象,情况就有些复杂了。
当我说我是一个beginner的时候,我会这样写:
class Car{ private String color; private String name; Car(String color, String name) { this.color = color; this.name = name; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Car [color=" + color + ", name=" + name + "]"; } } public class Test{
public static void main(String[] args) throws Exception {
Car car = new Car("白色", "奔驰"); Car cloneCar = car; System.out.println("car = " + car); System.out.println("cloneCar = " + cloneCar); } }
结果:
car = Car [color=白色, name=奔驰]
cloneCar = Car [color=白色, name=奔驰]
这里我们自定义了一个Car类,该类有color和name字段。
我们新建了一个Car实例,然后将该值赋值给cloneCar实例。(Car cloneCar = car;)
再看看打印结果,作为一个新手,拍了拍胸腹,对象复制不过如此!
难道真的是这样吗?我们试着改变cloneCar实例的name字段,再打印结果看看:
cloneCar.setName("大众"); System.out.println("car = " + car); System.out.println("cloneCar = " + cloneCar);
结果:
car = Car [color=白色, name=大众]
cloneCar = Car [color=白色, name=大众]
为什么改变cloneCar属性,car属性也发生了变化呢?
原因出在(cloneCar = car) 这一句。该语句的作用是将car的引用赋值给cloneCar,这样,car和cloneCar指向内存堆中同一个对象。
那么才能复制一个对象呢?
这就要用到我们的Object类了。它有11个方法,有两个protected的方法,其中一个为clone方法。
在Java中所有的类都是缺省的继承自Java语言包中的Object类的,查看它的源码。你会发现里面有一个访问限定符为protected的方法clone():
/* Creates and returns a copy of this object. The precise meaning of "copy" may depend on the class of the object. The general intent is that, for any object x, the expression: 1) x.clone() != x will be true 保证克隆对象将有单独的内存地址分配。 2) x.clone().getClass() == x.getClass() will be true, but these are not absolute requirements. 原始和克隆的对象应该具有相同的类类型,但它不是强制性的。 3) x.clone().equals(x) will be true, this is not an absolute requirement. 原始和克隆的对象应该是平等的equals()方法使用,但它不是强制性的。 */ protected native Object clone() throws CloneNotSupportedException;
仔细一看,它还是一个native方法,大家都知道native方法是非Java语言实现的代码,供Java程序调用的,因为Java程序是运行在JVM虚拟机上面的,要想访问到比较底层的与操作系统相关的就没办法了,只能由靠近操作系统的语言来实现。
因为每个类直接或间接的父类都是Object,因此它们都含有clone()方法,但是因为该方法是protected,所以都不能在类外进行访问。
要想对一个对象进行复制,就需要对clone方法覆盖。
一、为什么要克隆?
为什么需要克隆对象?直接new一个对象不行吗?
答案是:克隆的对象可能包含一些已经修改过的属性,而new出来的对象的属性都还是初始化时候的值,所以当需要一个新的对象来保存当前对象的“状态”就靠clone方法了。
那么我把这个对象的临时属性一个一个的赋值给我新new的对象不也行嘛?可以是可以,但是一来麻烦不说,二来,大家通过上面的源码都发现了clone 是一个 native 方法,就是快啊,在底层实现的。
常见的 Object a = new Object();Object b = a;这种形式的代码复制的是引用,即对象在内存中的地址,a和b对象仍然指向了同一个对象。
而通过clone方法赋值的对象跟原来的对象时同时独立存在的。
二、如何实现克隆
Java中有两种不同的克隆方法:浅克隆(ShallowClone)和深克隆(DeepClone)
(1)浅克隆:实现Cloneable接口并重写Object类中的clone()方法;
(2)深克隆:实现Serializable接口,通过对象的序列化和反序列化实现克隆;
Java语言提供的Cloneable接口和Serializable接口的代码非常简单,它们都是空接口,这种空接口也称为标识接口,标识接口中没有任何方法的定义,其作用是告诉JRE这些接口的实现类是否具有某个功能,如是否支持克隆、是否支持序列化等。
- 注意:基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种是方案明显优于使用Object类的clone方法克隆对象。让问题在编译的时候暴露出来总是优于把问题留到运行时。
三、浅克隆
Car类:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Car implements Cloneable { private String color; private String name; Car() { super(); } Car(String color, String name) { this.color = color; this.name = name; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Car [color=" + color + ", name=" + name + "]"; } }
Persion类:实现Cloneable接口,需要重写clone方法。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Persion implements Cloneable { private int age; private String name; private Car car; public Persion() { super(); } public Persion(int age, String name, Car car) { super(); this.age = age; this.name = name; this.car = car; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Car getCar() { return car; } public void setCar(Car car) { this.car = car; } @Override public String toString() { return "Persion [age=" + age + ", name=" + name + ", car=" + car + "]"; } /** * 实现Cloneable接口,并重写clone方法 */ @Override protected Object clone() throws CloneNotSupportedException { Persion persion = null; try { /** * 若要实现深克隆,此处就必须将对象中所有的复合数据类型统统单独复制拷贝一份, 但是实际开发中,无法确定对象中复合数据的种类和个数, * 因此一般不采用此种方式实现深克隆 */ persion = (Persion) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return persion; } }
测试类:
public class Test { public static void main(String[] args) throws Exception { Car car = new Car("白色", "奔驰"); Persion p1 = new Persion(24, "阿大", car); Persion p2 = (Persion) p1.clone(); p2.setAge(17); p2.setName("小黄"); p2.getCar().setName("大众"); System.out.println("p1 == p2 ? " + (p1 == p2)); System.out.println(p1); System.out.println(p2); } }
结果:小黄将阿大的奔驰变成大众。
修改基础数据结构数值不会影响其他对象,修改复合数据数值,全部都会受影响;
四、深克隆
Car类:必须实现Serializable接口
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Car implements Serializable { private static final long serialVersionUID = -6593855835779505248L; private String color; private String name; Car() { super(); } Car(String color, String name) { this.color = color; this.name = name; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Car [color=" + color + ", name=" + name + "]"; } }
Persion类:必须实现Serializable接口
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Persion implements Serializable { private static final long serialVersionUID = -630014110607564384L; private int age; private String name; private Car car; public Persion() { super(); } public Persion(int age, String name, Car car) { super(); this.age = age; this.name = name; this.car = car; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Car getCar() { return car; } public void setCar(Car car) { this.car = car; } @Override public String toString() { return "Persion [age=" + age + ", name=" + name + ", car=" + car + "]"; } }
CloneUtil - 克隆工具类:
class CloneUtil { @SuppressWarnings("unchecked") public static <T extends Serializable> T clone(T object) throws Exception { ByteArrayOutputStream bout = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bout); oos.writeObject(object); ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bin); // 此处不需要释放资源,说明:调用ByteArrayInputStream或ByteArrayOutputStream对象的close方法没有任何意义 // 这两个基于内存的流只要垃圾回收器清理对象就能够释放资源,这一点不同于对外部资源(如文件流)的释放 return (T) ois.readObject(); } }
测试:
public class Test{ public static void main(String[] args) throws Exception { Car car = new Car("白色", "奔驰"); Persion p1 = new Persion(24, "阿大", car); Persion p2 = CloneUtil.clone(p1); p2.setAge(17); p2.setName("小黄"); p2.getCar().setName("大众"); System.out.println("p1 == p2 ? " + (p1 == p2)); System.out.println(p1); System.out.println(p2); } }
结果:奔驰还是奔驰,大众还是大众。
由输出结果来看,p2对象的car值修改不影响p1对象的car值,即使用序列化可以实现对象的深拷贝。