Cloneable接口的目的是作为对象的一个mixin接口(mixin interface),表明这样的对象允许克隆(clone)。
遗憾的是,他并没有成功的达到这个目的。其主要的缺陷在于,他缺少一个clone方法,Object的clone方法是受保护的。
如果不借助于反射(reflection),就不能仅仅因为一个对戏那个实现了Cloneable,就可以调用clone。
demo:
语言之外的(extralinguistic)机制:无需调用构造器就可以创建对象。
如果你覆盖了非final类中的clone方法,则应该返回一个通过调用super.clone而得到的对象。
实际上,对于实现了Cloneable的类,我们总是期望他也提供一个功能适当的公有的clone方法。
通常情况下,除非该类的所有超类都提供了行为良好的clone实现,无论是共有的还是受保护的,否则,都不可能这么做。
如果你想在一个类中实现Cloneable,首先他的超类都需要提供行为良好的clone方法。
demo:
上面的clone方法返回的是PhoneNumber,而不是返回的Object,要记住,必须要在返回super.clone的结果之前将它转换。
当被克隆的对象里面包含的域引用了可变的对象时:
(实际上,clone方法就是另外一个构造器;你必须确保他能不会伤害到原始的对象,并确保正确的创建被克隆对象中的约束条件-invariant)
注意:如果上面的demo elements域是final的,上诉方式就不能正常的工作,clone方法是禁止给elements域赋新值的。
clone架构与引用可变对象的final域的正常用法是不相兼容的。
当被克隆的对象他有自己的散列桶数组,直接克隆就会出现问题,就需要单独的拷贝,如:
还要考虑在拷贝的过程中线程安全的问题,把被拷贝的对象设置为不可外部修改或者实现线程安全。
最好提供某些其他的途径来代替对象拷贝,或者干脆不提供这样的功能。
另一个实现对象拷贝的好办法是提供一个拷贝构造器或拷贝工厂。
遗憾的是,他并没有成功的达到这个目的。其主要的缺陷在于,他缺少一个clone方法,Object的clone方法是受保护的。
如果不借助于反射(reflection),就不能仅仅因为一个对戏那个实现了Cloneable,就可以调用clone。
demo:
- import java.util.LinkedList;
- import java.util.List;
- public class A implements Cloneable {
- private Integer id;
- private String name;
- private List<String> phones = new LinkedList<String>();
- public Integer getId() {
- return id;
- }
- public void setId(Integer id) {
- this.id = id;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public List<String> getPhones() {
- return phones;
- }
- public void setPhones(List<String> phones) {
- this.phones = phones;
- }
- @Override
- protected A clone() {
- try {
- return (A)super.clone();
- } catch (CloneNotSupportedException e) {
- e.printStackTrace();
- }
- return null;
- }
- }
- public class HelloWorld {
- public static void main(String[] args) {
- // cope1();
- cope2();
- }
- //在不借助于BeanUtils的实例复制方法下,会出现问题,当然,原因很简单,一个指针
- private static void cope1() {
- A old = new A();
- old.setId(1);
- old.setName("hello");
- A n = old;
- n.setName("world");
- System.out.println(old.getName());
- }
- /**
- * 我们让A实现了clone。但是你如果A里面含有很多可变的对象,就比较麻烦了,会引用与原来相同的数组
- (BeanUtils.copyProperties也会引用与原来相同的数组)。
- */
- private static void cope2() {
- A old = new A();
- old.setId(1);
- old.setName("hello");
- old.getPhones().add("da");
- A n = old.clone();
- n.setName("world");
- n.getPhones().set(0, "xiao");
- System.out.println(old.getName());
- System.out.println(old.getPhones().get(0));
- }
- }
语言之外的(extralinguistic)机制:无需调用构造器就可以创建对象。
如果你覆盖了非final类中的clone方法,则应该返回一个通过调用super.clone而得到的对象。
实际上,对于实现了Cloneable的类,我们总是期望他也提供一个功能适当的公有的clone方法。
通常情况下,除非该类的所有超类都提供了行为良好的clone实现,无论是共有的还是受保护的,否则,都不可能这么做。
如果你想在一个类中实现Cloneable,首先他的超类都需要提供行为良好的clone方法。
demo:
- // Adding a clone method to PhoneNumber
- import java.util.*;
- public final class PhoneNumber implements Cloneable {
- private final short areaCode;
- private final short prefix;
- private final short lineNumber;
- public PhoneNumber(int areaCode, int prefix,
- int lineNumber) {
- rangeCheck(areaCode, 999, "area code");
- rangeCheck(prefix, 999, "prefix");
- rangeCheck(lineNumber, 9999, "line number");
- this.areaCode = (short) areaCode;
- this.prefix = (short) prefix;
- this.lineNumber = (short) lineNumber;
- }
- private static void rangeCheck(int arg, int max,
- String name) {
- if (arg < 0 || arg > max)
- throw new IllegalArgumentException(name +": " + arg);
- }
- @Override public boolean equals(Object o) {
- if (o == this)
- return true;
- if (!(o instanceof PhoneNumber))
- return false;
- PhoneNumber pn = (PhoneNumber)o;
- return pn.lineNumber == lineNumber
- && pn.prefix == prefix
- && pn.areaCode == areaCode;
- }
- @Override public int hashCode() {
- int result = 17;
- result = 31 * result + areaCode;
- result = 31 * result + prefix;
- result = 31 * result + lineNumber;
- return result;
- }
- /**
- * Returns the string representation of this phone number.
- * The string consists of fourteen characters whose format
- * is "(XXX) YYY-ZZZZ", where XXX is the area code, YYY is
- * the prefix, and ZZZZ is the line number. (Each of the
- * capital letters represents a single decimal digit.)
- *
- * If any of the three parts of this phone number is too small
- * to fill up its field, the field is padded with leading zeros.
- * For example, if the value of the line number is 123, the last
- * four characters of the string representation will be "0123".
- *
- * Note that there is a single space separating the closing
- * parenthesis after the area code from the first digit of the
- * prefix.
- */
- @Override public String toString() {
- return String.format("(%03d) %03d-%04d",
- areaCode, prefix, lineNumber);
- }
- @Override public PhoneNumber clone() {
- try {
- return (PhoneNumber) super.clone();
- } catch(CloneNotSupportedException e) {
- throw new AssertionError(); // Can't happen
- }
- }
- public static void main(String[] args) {
- PhoneNumber pn = new PhoneNumber(707, 867, 5309);
- Map<PhoneNumber, String> m
- = new HashMap<PhoneNumber, String>();
- m.put(pn, "Jenny");
- System.out.println(m.get(pn.clone()));
- }
- }
上面的clone方法返回的是PhoneNumber,而不是返回的Object,要记住,必须要在返回super.clone的结果之前将它转换。
当被克隆的对象里面包含的域引用了可变的对象时:
(实际上,clone方法就是另外一个构造器;你必须确保他能不会伤害到原始的对象,并确保正确的创建被克隆对象中的约束条件-invariant)
- // A cloneable version of Stack
- import java.util.Arrays;
- public class Stack implements Cloneable {
- private Object[] elements;
- private int size = 0;
- private static final int DEFAULT_INITIAL_CAPACITY = 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; // Eliminate obsolete reference
- return result;
- }
- public boolean isEmpty() {
- return size == 0;
- }
- @Override public Stack clone() {
- try {
- Stack result = (Stack) super.clone();
- result.elements = elements.clone();
- return result;
- } catch (CloneNotSupportedException e) {
- throw new AssertionError();
- }
- }
- // Ensure space for at least one more element.
- private void ensureCapacity() {
- if (elements.length == size)
- elements = Arrays.copyOf(elements, 2 * size + 1);
- }
- // To see that clone works, call with several command line arguments
- public static void main(String[] args) {
- args = new String[]{"hello","world"};
- Stack stack = new Stack();
- for (String arg : args)
- stack.push(arg);
- Stack copy = stack.clone();
- while (!stack.isEmpty())
- System.out.print(stack.pop() + " ");
- System.out.println();
- while (!copy.isEmpty())
- System.out.print(copy.pop() + " ");
- }
- }
注意:如果上面的demo elements域是final的,上诉方式就不能正常的工作,clone方法是禁止给elements域赋新值的。
clone架构与引用可变对象的final域的正常用法是不相兼容的。
当被克隆的对象他有自己的散列桶数组,直接克隆就会出现问题,就需要单独的拷贝,如:
- 错误:
- public HashTable clone(){
- HashTable result = (HashTable)super.clone();
- result.buckets = buckets.clone();
- return reslut;
- }
- 正确:深度拷贝
- public HashTable clone(){
- HashTable result = (HashTable)super.clone();
- result.buckets = new Entry[buckets.length];
- for(int i=0;i<buckets.length;i++){
- if(buckets[i] != null){
- result.buckets[i] = buckets[i].deepCopy();
- }
- }
- return reslut;
- }
还要考虑在拷贝的过程中线程安全的问题,把被拷贝的对象设置为不可外部修改或者实现线程安全。
最好提供某些其他的途径来代替对象拷贝,或者干脆不提供这样的功能。
另一个实现对象拷贝的好办法是提供一个拷贝构造器或拷贝工厂。