基本数据类型(boolean,char,byte,short,float,double,long)的复制很简单,比如:
int width = 5;
int height = width;
基本数据类型进行这样复制是没有问题的。
按照上面的方法进行对象的复制,首先自定义一个汽车类,该类有一个color 属性,然后新建一个汽车实例car,然后将car 赋值给car1 即car1= car;
代码和结果如下:
public static void main(String[] args) {
Car car = new Car();
car.setColor("white");
Car car1 = car;
System.out.println("car color: " + car.getColor());
System.out.println("car1 color: " + car1.getColor());
}
private static class Car {
private Car() {
}
private String color;
public void setColor(String color) {
this.color = color;
}
public String getColor() {
return color;
}
}
输出结果:
car color: white
car1 color: white
从打印来看没有什么问题, 那么我们对代码更改如下:
public static void main(String[] args) {
Car car = new Car();
car.setColor("white");
Car car1 = car;
car1.setColor("blue");
System.out.println("car color: " + car.getColor());
System.out.println("car1 color: " + car1.getColor());
}
输出结果:
car color: blue
car1 color: blue
为什么会出现这种结果呢? 给car1 赋值 结果car 的值也变了。
问题的原因是,car1= car 这行语句,该语句的作用是将car的引用赋值给car1,其实car 与car1 指向内存堆中的同一个对象,如下图:
对car 和car1 操作实际上市操作的同一个对象。
如何复制对象呢 ? 我们知道所有的类都集成Object, 方法如下:
protected Object clone() throws CloneNotSupportedException {
if (!(this instanceof Cloneable)) {
throw new CloneNotSupportedException("Class " + getClass().getName() +
" doesn't implement Cloneable");
}
return internalClone();
}
/*
* Native helper method for cloning.
*/
private native Object internalClone();
从代码中可以看出要实现clone 方法的类必须实现Clonenable 接口,最终是通过native方法,大家都知道native方法是非Java语言实现的代码,供Java程序调用的,因为Java程序是运行在JVM虚拟机上面的,要想访问到比较底层的与操作系统相关的就没办法了,只能由靠近操作系统的语言来实现。
第一次声明保证克隆对象将有单独的内存地址分配。
第二次声明表明,原始和克隆的对象应该具有相同的类类型,但它不是强制性的。
第三声明表明,原始和克隆的对象应该是平等的equals()方法使用,但它不是强制性的。
因为每个类直接或间接的父类都是Object,因此它们都含有clone()方法,但是因为该方法是protected,所以都不能在类外进行访问。要想对一个对象进行复制,就需要对clone方法覆盖。
为什么要Clone
有时候需要clone 一个对象修改过的属性,然而通过new创建的对象的属性是初始值,所以当需要一个新的对象来保存当前对象的“状态”就靠clone方法了。如果把对象的临时属性一个一个的赋值给我新new的对象,操作起来比较麻烦,并且速度没有底层实现的快。
如何实现clone
clone 分为两种, 浅克隆(ShallowClone)和深克隆(DeepClone)。在Java语言中,数据类型分为值类型(基本数据类型)和引用类型,值类型包括int、double、byte、boolean、char等简单数据类型,引用类型包括类、接口、数组等复杂类型。浅克隆和深克隆的主要区别在于是否支持引用类型的成员变量的复制,下面将对两者进行详细介绍。
浅克隆
1、被复制的类需要实现Clonenable接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常), 该接口为标记接口(不含任何方法)
2、 覆盖clone()方法,访问修饰符设为public。方法中调用super.clone()方法得到需要的复制对象。(native为本地方法)
下面对Car 类进行改造:
public class TestJava {
public static void main(String[] args) {
Car car = new Car();
car.setColor("white");
Car car1 = (Car) car.clone();
car1.setColor("blue");
if (car1 != null) {
System.out.println("car color: " + car.getColor());
}
System.out.println("car1 color: " + car1.getColor());
System.out.println("car == car1?" + (car == car1));
}
private static class Car implements Cloneable {
private Car() {
}
private String color;
public void setColor(String color) {
this.color = color;
}
public String getColor() {
return color;
}
@Override
protected Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
}
通过查看打印信息可以看出car 和 car1 不是指向的同一个对象,上面实现的这种clone 为浅clone, 还有一种clone 为深clone
深clone
我们对Car 类进行改造,增加一个Engine属性
public class TestJava {
public static void main(String[] args) {
Engine engine = new Engine();
engine.setModel("CA");
Car car = new Car();
car.setEngine(engine);
Car newCar = (Car) car.clone();
newCar.getEngine().setModel("CY");
System.out.println("Car Engine Model:" + car.getEngine().getModel());
System.out.println("newCar Engine Model: " + newCar.getEngine().getModel());
}
private static class Car implements Cloneable {
private Car() {
}
private Engine engine;
private String color;
public void setColor(String color) {
this.color = color;
}
public String getColor() {
return color;
}
public void setEngine(Engine engine) {
this.engine = engine;
}
public Engine getEngine() {
return engine;
}
@Override
protected Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
private static class Engine {
private String model;
public void setModel(String model) {
this.model = model;
}
public String getModel() {
return model;
}
}
}
Car Engine Model:CY
newCar Engine Model: CY
通过打印可以看改变了newCar对象的Engine 属性 car 的Engine 属性也变了,
原因是浅复制只是复制了engine变量的引用,并没有真正的开辟另一块空间,将值复制后再将引用返回给新对象。
所以,为了达到真正的复制对象,而不是纯粹引用复制。我们需要将Engine类可复制化,并且修改clone方法,完整代码如下:
public class TestJava {
public static void main(String[] args) {
Engine engine = new Engine();
engine.setModel("CA");
Car car = new Car();
car.setEngine(engine);
Car newCar = (Car) car.clone();
newCar.getEngine().setModel("CY");
System.out.println("Car Engine Model:" + car.getEngine().getModel());
System.out.println("newCar Engine Model: " + newCar.getEngine().getModel());
}
private static class Car implements Cloneable {
private Car() {
}
private Engine engine;
private String color;
public void setColor(String color) {
this.color = color;
}
public String getColor() {
return color;
}
public void setEngine(Engine engine) {
this.engine = engine;
}
public Engine getEngine() {
return engine;
}
@Override
protected Object clone() {
Car car = null;
try {
car = (Car) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
car.engine = (Engine) engine.clone();
return car;
}
}
private static class Engine implements Cloneable{
private String model;
public void setModel(String model) {
this.model = model;
}
public String getModel() {
return model;
}
@Override
protected Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
}
输出结果:
Car Engine Model:CA
newCar Engine Model: CY
输出结果与我们的想法一致
最后Java util 中clone 的实现“
/**
* Return a copy of this object.
*/
public Object clone() {
Date d = null;
try {
d = (Date)super.clone();
if (cdate != null) {
d.cdate = (BaseCalendar.Date) cdate.clone();
}
} catch (CloneNotSupportedException e) {} // Won't happen
return d;
}
总结 浅克隆 与深克隆
1、浅克隆
在浅克隆中,如果原型对象的成员变量是基本类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。
简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。
2、深克隆
在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。
简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。
序列化实现深克隆
序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。
Java语言提供的Cloneable接口和Serializable接口的代码非常简单,它们都是空接口,这种空接口也称为标识接口,标识接口中没有任何方法的定义,其作用是告诉JRE这些接口的实现类是否具有某个功能,如是否支持克隆、是否支持序列化等。
实现方法:
public class TestJava {
public static void main(String[] args) {
Child child = new Child();
child.name = "Tony";
Parent parent = new Parent();
parent.setChild(child);
Parent newParent = parent.clone();
newParent.getChild().name = "Steven";
System.out.println("parent child name:" + parent.getChild().name);
System.out.println("new parent child name:" + newParent.getChild().name);
}
public static class Parent implements Serializable {
private static final long serialVersionUID = 369285298572941L;
private Child child;
public void setChild(Child child) {
this.child = child;
}
public Child getChild() {
return child;
}
public Parent clone() {
Parent parent = 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);
parent = (Parent) ois.readObject();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return parent;
}
}
public static class Child implements Serializable {
private static final long serialVersionUID = 872390113109L;
public String name;
@Override
public String toString() {
return "name :" + name;
}
}
}
实现对象克隆有两种方式:
1). 实现Cloneable接口并重写Object类中的clone()方法;
2). 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。
基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种是方案明显优于使用Object类的clone方法克隆对象。让问题在编译的时候暴露出来总是优于把问题留到运行时。