Object Cloning in Java - Quiz

Total: 5 questions

1. 

What do you need to do to make objects of a class cloneable with clone(), and what is the Cloneable interface for?

The clone() method is declared in the Object class as protected and makes a shallow copy by default. To make cloning work, the class must:

  • implement the Cloneable interface;
  • override clone() as public and call super.clone() inside it.

Cloneable is a marker interface: it has no methods and no constants, it only allows Object.clone() to copy instances of the class. If the class does not implement Cloneable, super.clone() throws CloneNotSupportedException at runtime. If clone() is not overridden as public, calling car.clone() from another class does not compile: clone() has protected access in java.lang.Object. Since Java 5 the overriding method can return the class itself (public Car clone()) — the covariant return type removes the need for a cast.

2. 

What is the difference between a shallow copy and a deep copy in Java?

A shallow copy copies only the field values: the clone is a new object (clone != original), but its reference fields point to the same nested objects. A change to a nested object made through the clone is visible in the original: after clonedCar.getDriver().setName("Vic") the driver changes for both cars. This is how Object.clone(), clone() of an array of objects, ArrayList.clone() and new ArrayList<>(list) work by default.

A deep copy copies nested mutable objects as well, so the clone and the original share nothing. Shared references to immutable objects (String, Integer, LocalDate) are safe and need no copy; what must be copied are mutable fields — Date, arrays, collections, objects of your own classes.

3. 

How do you implement a deep copy with the clone() method?

Call super.clone() first, then copy every mutable reference field by hand and assign the copy to the field of the clone:

@Override
public Car clone() {
    try {
        Car copy = (Car) super.clone();
        if (driver != null) {
            copy.driver = driver.clone();
        }
        return copy;
    } catch (CloneNotSupportedException e) {
        throw new AssertionError(e);
    }
}

The method can be declared without throws CloneNotSupportedException: the class implements Cloneable, so the exception cannot occur, and there is no reason to push a checked exception onto the caller (Joshua Bloch recommends the same in Effective Java). If a nested object holds mutable objects of its own, copy them the same way on every level. Do not create the copy with new: subclasses would then get an object of the wrong type. And this approach does not work for final fields — a copy cannot be assigned to them.

4. 

What are a copy constructor and a static copy factory, and why are they better than clone()?

A copy constructor takes an object of the same class and copies its fields: public Car(Car other). A static copy factory is the same idea as a static method with a meaningful name, for example public static Car copyOf(Car other); it is handy when the copy should be returned as an interface or a subtype.

Advantages over clone():

  • no Cloneable and no checked exceptions;
  • works with final fields;
  • it is an ordinary constructor: validation and initialization run, while Object.clone() bypasses constructors;
  • it shows explicitly which fields are copied deeply.

On its own a copy constructor, like clone(), copies references. For a deep copy, copy nested objects explicitly: new Driver(other.driver), and a list element by element: cars.stream().map(Car::new).collect(Collectors.toCollection(ArrayList::new)). For your own classes this is the default choice; Bloch advises keeping clone() for arrays.

5. 

How do you make a deep copy via serialization, and which cloning approach should you choose in which case?

The object is written to a byte array with an ObjectOutputStream over a ByteArrayOutputStream and immediately read back with an ObjectInputStream over a ByteArrayInputStream. The result is a deep copy of the whole object graph without copy code for each field. The requirement is that every class in the graph implements Serializable, otherwise you get NotSerializableException; transient fields get default values in the copy (null, 0, false). This approach is much slower than the others.

What to choose:

  • copy constructor — the default for your own classes: explicit, works with final fields, no exceptions;
  • copy factory (copyOf) — when you need to return an interface/subtype or want a meaningful name;
  • clone() — for arrays and subclasses of classes that already support clone();
  • serialization — for a rare deep copy of a complex graph, not in hot code.
Page 1 of 1