post-images/overriding-clone/cover.jpg

Overriding clone()

Hello my friend, in this article, I will examine the clone() method of the Object class. My goal to pass on what I have learned to you. Let's start.

What do you do when you want to use a copy of an object? Isn't the first thing that comes to mind to create a new object and assign the original object to the object you just created? I can say that Java will not work very well due to the pass by value feature. Well, have you ever heard of or used the clone() method? I haven't had a chance to use it. With this article, I wish to increase our experience or at least to have information that such a thing exists in our ears. Returning to our topic, it is written to declare that the object you have written in general is cloning is allowed. But it does not contain the cloning method. For the first stage, I have no information on how to clone my object. This item basically exists to discuss how and when to implement a bona fide cloning method and to look for alternatives.

What happens if I call the clone() method of the object I wrote without any implementation? Returns a copy of the object you want to clone with the same fields. Of course, if it is cloneable, if not, CloneNotSupportedException error will be encountered. By cloneable is meant here the implementation of the Cloneable interface. Normally we use interfaces to tell the class what to do, but here we use it to change the behavior of a method in the superclass. So, is the clone() method of the Object class enough for us? Partly yes, partly no. As with other Object class methods, there is a set of rules that must be followed when applying this method - a contract. But for the clone() method, these items cannot be said to be robust. The contract clauses are:

  • x.clone() != x must be valid.
  • x.clone().getClass() == x.getClass() should be valid but not an absolute requirement
  • x.clone().equals(x) must be valid but not an absolute requirement

In order to use the clone() method from within the Object class, the super.clone() method must be called and all subclasses must comply with this rule. When this rule is met, the theory of x.clone().getClass() == x.getClass() is expected to be realized. The result should be two completely independent objects.

// Clone method for class with no references to mutable state
@Override public PhoneNumber clone() {
    try {
        return (PhoneNumber) super.clone();
    } catch (CloneNotSupportedException e) {
        throw new AssertionError();  // Can't happen
    }
}

Another thing to note here is immutable classes, such classes should not be copied in vain.

When we want to clone our PhoneNumber class, which does not contain any immutable classes, we can use the clone() method of the Object class with the Clonable implementation. With the casting process, we can specify the type of our object as PhoneNumber. This process is guaranteed to be successful.

When we want to clone another class that contains any immutable class, things can get messy and the results can be disastrous. You'll understand better what I mean when we consider the example below.

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);
    }
}

As you can see, in a stack structure, as long as an element is added to the stack and the stack capacity is not enough, it will expand itself. Suppose we want to clone this structure. Let's call the clone() method with super.clone(). What do we encounter? Since the size variable is mutable, it preserves its exact value in the cloning process. But there is one place I want you to pay attention to. The element variable that stack holds will refer to its original state when cloned. Changing the original reference will break the clone, and changing the reference in the clone will break the original literals. This will cause you to see incorrect results after cloning or even a NullPointerException error. To solve this situation, the elements field can also be cloned.

// Clone method for class with references to mutable state
@Override public Stack clone() {
    try {
        Stack result = (Stack) super.clone();
        result.elements = elements.clone();
        return result;
    } catch (CloneNotSupportedException e) {
        throw new AssertionError();
    }
}

Finally, another issue that I want you to pay attention to is the structures that are nested. For example, consider a tree structure and if I use the clone() method of the Object class when I want to copy this tree structure, I will copy the one-to-one fields and only copy the initial values of the tree. Instead, all the individual notes will have to be traversed in depth. You can reinforce it with the following example.

We have a recursive HashTable class:

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;  
        }
    }
    ...
}

If we apply what we have learned with the clone() method;

// Broken clone method - results in shared mutable state!
@Override public HashTable clone() {
    try {
        HashTable result = (HashTable) super.clone();
        result.buckets = buckets.clone();
        return result;
    } catch (CloneNotSupportedException e) {
        throw new AssertionError();
    }
}

As in the 'Stack', we have broken the dependency of the 'buckets' variable for the 'HashTable' as well. But it is not enough, since there is a nested recursive structure here, we need to be able to clone this whole structure. We can solve this by adding a recursive cloning construct inside our class.

... 
// Recursively copy the linked list headed by this Entry
Entry deepCopy() {
    return new Entry(key, value, next == null ? null : next.deepCopy());
}
... 

Depending on this method, we can change the clone() implementation as follows.

... 
@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();
    }
}
... 

ta daaaa🎉 Congratulations on making an exact copy of your original class 👏🏻

These are the things that I wanted to talk about and considered important in Clonable. I will continue through the book and continue to increase your knowledge on other subjects, stay tuned 🙋🏻‍♂️

JavaEffective JavaMethods Common to All ObjectsClonableclone()OverridingSoftware Contract