一、clone()方法
clone意思是克隆、复制。在Java语言中,当对象调用clone()方法时,就会复制已有的对象。clone()方法在根类Object中定义如下。
/**
* Class Object is the root of the class hierarchy. Every class has Object as a superclass.
* All objects, including arrays, implement the methods of this class.
*/
public class Object {
/**
* 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 {@code x}, the expression: x.clone() != x will be true,
* and that the expression: x.clone().getClass() == x.getClass() will be true, but these are not absolute requirements.
* While it is typically the case that: x.clone().equals(x) will be true, this is not an absolute requirement.
*/
protected native Object clone() throws CloneNotSupportedException;
}
从上面对clone方法的注解可知clone方法的通用约定:对于任意一个对象x,表达式①x.clone != x将会是true;表达式②x.clone().getClass()==x.getClass()将会是true,但不是绝对的。通常情况下,表达式③x.clone().equals(x)将会是true,但是这也不是绝对的。
从源代码可知,根类Object的clone方法是用protected关键字修饰,这样做是为避免我们创建每一个类都默认具有克隆能力。这样做造成的后果就是:对于那些简单使用一下这个类的客户程序员来说,他们不会默认使用这个方法;其次,我们不能利用指向基础类的一个句柄来调用clone方法。在编译期间,实际上是通知我们对象不可克隆的一种方式。
二、Cloneable接口
要使类具有克隆能力能力时,需要实现Cloneable接口,实现它的目的是作为一个对象的一个mixin(混入)接口,表明这个对象是允许克隆的。它的源码如下:
/**
* A class implements the Cloneable interface to indicate to the {@link java.lang.Object#clone()} method that
* it is legal for that method to make a field-for-field copy of instances of that class.
*/
public interface Cloneable {
}
可以看出Cloneable是一个空接口(标记接口),它决定了Object中受保护的clone方法的实现行为:如果一个类实现了Cloneable接口,Object的clone方法就返回这个对象的逐域拷贝,否则就抛出CloneNotSupportedException异常。如果实现了这个接口,类和它所有的超类都无需调用构造器就可以创建对象。下面是一个简单的clone方法应用。
public class Person implements Cloneable {
private String name;
private Integer age;
private String sex;
public Person() {
super();
}
public Person(String name, Integer age, String sex) {
super();
this.name = name;
this.age = age;
this.sex = sex;
}
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", sex=" + sex + "]";
}
public Person clone() throws CloneNotSupportedException {
return (Person) super.clone();
}
public static void main(String[] args) {
Person person = new Person("Jachen", 23, "boy");
System.out.println("person:" + person);
try {
Person person2 = person.clone();
System.out.println("person2:" + person2);
System.out.println("person.equals(person2):"+ person2.equals(person));
person2.setName("Anna");
person2.setSex("girl");
person2.setAge(22);
System.out.println(person2.getClass() == person.getClass());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
运行结果如下图所示:
由运行结果可知,当person对象调用clone方法后复制了一个跟它“相似”的对象,可是调用equals方法后,得知他们在堆中的地址是不同的(JVM中堆、栈简介),内存图结构如下:
三、Object.clone()的运行效果
根类Object中的clone()方法负责建立正确的存储容量,并通过“按位复制”将所有二进制从原始对象中复制到新对象的存储空间。也就是说,它并不只是预留存储空间以及复制一个对象--实际需要调查出欲复制的新对象准确大小,然后再复制那个对象。由于这些工作都是由根类定义的clone()方法内部代码进行的,这个过程需要用RTTI(运行时类型鉴定)来判断欲克隆对象的实际大小。采用这种方式,clone()方法便可建立起正确数量的存储空间,并对那个类型进行正确的按位复制。
克隆过程的第一个部分通常都应该是调用super.clone()。通过进行一次准确的复制,这样做可为后续的克隆进程建立起一个良好的基础。随后,可采取另一些必要的操作,以完成最终的克隆。
通常可在从一个能克隆的类里调用 super.clone(),以确保所有基础类行动(包括 Object.clone())能够进行。随着是为对象内每个句柄都明确调用一个 clone();否则那些句柄会别名变成原始对象的句柄。构建器的调用也大致相同——首先构造基础类,然后是下一个衍生的构建器……以此类推,直到位于最深层的衍生构建器。区别在于 clone()并不是个构建器,所以没有办法实现自动克隆。为了克隆,必须由自己明确进行。
若新建一个类,它的基础类会默认为Object,并默认为不具备克隆能力。只要不明确地添加克隆能力,这种能力便不会自动产生。但我们可以在任何层添加它,然后便可从那个层开始向下具有克隆能力。
四、深克隆
上面讲的是“浅复制”,接下来我们来讲解“深复制”。如果对象中包含的域引用了可变的对象,使用上述这种简单的clone实现(super.clone())可能会导致灾难性的后果,如下面代码所示。
import java.util.Arrays;
import java.util.EmptyStackException;
//栈类定义
public class Stack implements Cloneable{
private Object[] elements;//用数组来存储栈的元素
private int size = 0;//栈中元素的个数
private static final int DEFAULT_INITIAL_CAPACITY = 16;//默认初始化大小为16
public Stack(){
this.elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
//压入栈
public void push(Object e){
ensureCapacity();
elements[size++] = e;
}
//弹出栈信息
public Object pop(){
if(size == 0){
throw new EmptyStackException();
}
Object result = elements[--size];
elements[size] = null;
return result;
}
//确保栈的容量
private void ensureCapacity(){
if(elements.length == size){
elements = Arrays.copyOf(elements, 2*size + 1);
}
}
//克隆栈对象
public Stack clone () throws CloneNotSupportedException{
return (Stack) super.clone();
}
public static void main(String[] args) throws CloneNotSupportedException {
Stack stack = new Stack();
stack.push("Jachen");
stack.push("Anna");
stack.push("Jack");
Stack cloneStack = stack.clone();
cloneStack.pop();
for(int i = 0;i<stack.size;i++){
System.out.print(stack.elements[i] + ",");
}
}
}
当clone方法仅仅返回super.clone();得到这样的Stack实例:其size域具有正确的值,但是它的elements域将引用于原始Stack实例相同的数组。上面程序运行结果如下:
其内存中的情况如下图所示:
如果调用Stack类中唯一的构造器,这种情况就永远不会发生。实际上,clone方法就是另一个构造器,你必须确保它不会伤害到原始的对象,并确保正确地创建被克隆对象中的约束条件。为了使Stack类中clone方法正常工作,它必须要拷贝栈内部的信息,clone方法中代码修改如下:
public Stack clone() throws CloneNotSupportedException{
Stack stack = (Stack)super.clone();
stack.elements = elements.clone();
return stack;
}
五、必要时进行保护性拷贝
在Java安全的语言中,如果不采取一些措施,还是无法保证与其他类隔离开来。假设类的客户端尽其所能来破坏这个类的约束条件,因此你必须保护性地设计程序。例如下面的程序可能会受到破坏。
import java.util.Date;
public class Period {
private final Date start;
private final Date end;
public Period(Date start,Date end){
if(start.compareTo(end) > 0){
throw new IllegalArgumentException();
}
this.start = start;
this.end = end;
}
public Date getStart() {
return start;
}
public Date getEnd() {
return end;
}
public static void main(String[] args) {
Date start = new Date();
Date end = new Date();
Period period = new Period(start, end);
end.setYear(2017);
}
}
上面的代码会使Period实例内部信息遭受到攻击,需要对构造器的每个可变参数进行保护性拷贝。
public Period(Date start,Date end){
this.start = new Date(start.getTime());
this.end = new Date(end.getTime());
if(this.start.compareTo(this.end) > 0){
throw new IllegalArgumentException();
}
}
保护性拷贝是在检查参数的有效性之前进行的,并且有效性检查是针对拷贝之后的对象,而不是针对原始的对象。
每当编写方法或者构造器时,如果它要允许客户提供的对象进入到内部数据结构中,有必要考虑下客户提供的对象是否有可能是可变的。如果是,就考虑类是否能容忍这种变化,如果答案是否定的,则由必要进行保护性拷贝,并且让拷贝之后的对象而不是原始对象进入数据结构中。
在内部组件被返回给客户端之前,不管类是否为不可变,在把一个指向内部可变组件的引用返回给客户端之前,应该返回保护性拷贝。长度非零的数组总是可变的。因此,在把内部数组返回给客户端之前,应该总是要进行保护性拷贝。
简而言之,如果类是不可变的,一般不需要进行保护性拷贝。若类具有从客户端得到或者返回到客户端的可变组件,类就必须保护性地拷贝这些组件。如果拷贝成本受到限制,并且类信任客户端不会不恰当地修改组件,就可以在文档中指明客户端的职责是不得修改受到影响的组件,以此来代替保护性拷贝。
总之,所有实现了Cloneable接口的类都应该用一个公有的方法覆盖clone方法。此方法首先调用super.clone,然后修正任何需要修正的域。一般情况下,这意味着要拷贝任何包含内部“深层结构”的可变对象,并用指向新对象的引用代替原来指向这些对象的引用。如果该类只包含基本类型的域,或者指向不可变对象的引用,那多半的情况下是没有域需要修正的。
其实实现Cloneable接口具有很多问题,很多接口都不应该扩展这个接口,也不应该实现这个接口。因此很多程序员从来不去覆盖clone方法,也从来不去调用它,除非拷贝数组。所以,慎重选择覆盖clone()方法。