Item 11: Override clone judiciously
The Cloneable interface is intended to permit cloning. Unfortunately, it fails to serve this purpose. Its primary flaw is that it lacks a clone method.
Cloneable fails to function as an interface because it lacks a public clone method.
-
1.The disadvantage of the Cloneable interface:
- lacks a clone method && Object ’s clone method is protected.
- (Without resorting to reflection) CANNOT invoke the clone method on an object merely because it implements Cloneable.
- (Even with reflective) there is no guarantee that the object has an accessible clone method.
- lacks a clone method && Object ’s clone method is protected.
-
2.What does Cloneable do? (given that it contains no methods?)
- if a class implements Cloneable , Object ’s clone method returns a field-by-field copy of the object;
- otherwise, it throws CloneNotSupportedException.
The Cloneable interface is a highly atypical use of interfaces
-
3.The precise meaning of “copy”(The general contract)
- x.clone() !/= x
- [Too weak] x.clone().getClass() /=/= x.getClass()
- they extend a class and invoke super.clone from the subclass, the returned object will be an instance of the subclass.
- [like automatic constructor chaining]if you override the clone method in a nonfinal class, you should return an object obtained by invoking super.clone .
- x.clone().equals(x)
- [Too strong: Normally, no constructors are called] A well-behaved clone method can call constructors, If the class is final, clone can even return an object created by a constructor.
-
4.In practice
- A class that implements Cloneable is expected to provide a properly functioning public clone method.
- [Method 1] superclasses provide well-behaved clone methods
- covariant return types: Object => PhoneNumber(as of release 1.5)
-
an overriding method’s return type to be a subclass of the overridden method’s return type.
// Suppose you want to implement Cloneable in a class whose superclasses provide well-behaved clone methods. // If every field contains a primitive value or a reference to an immutable object, the returned object may be exactly what you need // provide public access to Object ’s protected clone method: @Override public PhoneNumber clone() { try { return (PhoneNumber) super.clone(); } catch(CloneNotSupportedException e) { throw new AssertionError(); // Can't happen } } ---------------------------------------------------------------------- // If an object contains fields that refer to mutable objects, using the simple // clone implementation shown above can be disastrous. public class Stack { 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; } // Ensure space for at least one more element. private void ensureCapacity() { if (elements.length == size) elements = Arrays.copyOf(elements, 2 * size + 1); } } // Merely returns super.clone() // (1)size field [correct] // (2)elements field [wrong: it refers to the same array] // could result in NullPointerException
- [Method 2] clone recursively
- In effect, the clone method functions as another constructor;
- you must ensure no harm to establish invariants on the clone.
- [NOTICE]the clone architecture is incompatible with normal use of final fields referring to mutable objects
-
In order to make a class cloneable, it may be necessary to remove final modifiers from some fields.
@Override public Stack clone() { try { Stack result = (Stack) super.clone(); result.elements = elements.clone(); //As of release 1.5 return result; } catch (CloneNotSupportedException e) { throw new AssertionError(); } }
//NOT WORK if the elements field were final
-
- [Problem] clone recursively.
-
[For Example]a hash table whose internals consist of an array of buckets, each of which references the first entry in a linked list of key-value pairs or is null if the bucket is empty.
// works fine if the buckets aren’t too long // it consumes one stack frame for each element in the list // If the list is long, this could easily cause a stack overflow. public class HashTable implements Cloneable { private Entry[] buckets = ...; private static class Entry { final Object key; Object value; Entry next; Entry(Object key, Object value, Entry next) { this.key = key; this.value = value; this.next = next; } // Recursively copy the linked list headed by this Entry Entry deepCopy() { return new Entry(key, value, next == null ? null : next.deepCopy()); } } @Override public HashTable clone() { try { 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 result; } catch (CloneNotSupportedException e) { throw new AssertionError(); } } ... // Remainder omitted } --------------------------------------------------------------------------------- //replace the recursion in deepCopy with iteration // Iteratively copy the linked list headed by this Entry Entry deepCopy() { Entry result = new Entry(key, value, next); for (Entry p = result; p.next != null; p = p.next) p.next = new Entry(p.next.key, p.next.value, p.next.next); return result; }
-
- [Method 3] “helper method”
- it should be either final or private
- In the case of our HashTable example, a put(key, value) could be used to initialize the buckets field.
- [Problem]clone may invoke an overridden method => lead to corruption
- [Method 1] superclasses provide well-behaved clone methods
- A class that implements Cloneable is expected to provide a properly functioning public clone method.
-
5.CloneNotSupportedException
- If a class that is designed for inheritance (Item 17) overrides clone ,
- it should be declared protected ,
- it should be declared to throw CloneNotSupportedException
- the class should not implement Cloneable .
- If a class that is designed for inheritance (Item 17) overrides clone ,
-
6.A thread-safe class implement Cloneable
- its clone method must be properly synchronized just like any other method (Item 66).
-
7.Options
- All classes that implement Cloneable should override clone with a public method whose return type is the class itself
- first call super.clone
- **fix any fields that need to be fixed
- [Exception] unique ID / creation time
- A copy constructor or copy factory
- A copy constructor: public Yum(Yum yum);
- A copy factory: public static Yum newInstance(Yum yum);
- [Advantages]:
- don’t rely on a risk-prone object creation mechanism
- don’t demand unenforceable adherence to conventions
- don’t conflict with the proper use of final fields
- don’t throw unnecessary checked exceptions
- don’t require casts
- conversion constructors & conversion factories
- Interface-based copy constructors and factories allow the client to choose the implementation type of the copy rather than forcing the client to accept the implementation type of the original.
- You want to copy HashSet s as a TreeSet [clone method X] [conversion constructor V]
- [Disadvantages]:
- impossible to put a copy constructor or factory in an interface
- [NO Implementation] it doesn’t make sense for immutable classes to support object copying
- All classes that implement Cloneable should override clone with a public method whose return type is the class itself
Given all of the problems associated with Cloneable , it’s safe to say that other interfaces should not extend it, and that classes designed for inheritance (Item 17) should not implement it.
-
Conclusion:
- (1) Never to override the clone method and never to invoke it (Exception: copy arrays)
- (2) Design a class for inheritance. If not to provide a well-behaved protected clone method, it will be impossible for subclasses to implement Cloneable .