Java, Copy Constructors, and clone()

The need to copy objects come up frequently in object-oriented programming. The are many times when an object needs to be copied, so a new object with the same state can be modified.

There are several solutions for this. Which one is best? That's, unfortunately, debatable.

Copy Constructors Considered Harmful

The reasoning behind this statement is fairly simple. I'll, uh, borrow an example found on the web.

public class Dog {
    public int age;
    public int weight;

    public Dog() { }

    public Dog(Dog original) {
        age = original.age;
        weight = original.weight;
    }
}

Fairly simple, right? Dog(Dog) is a copy constructor - it creates a copy of the original Dog object when invoked. What's the problem? Well, let's throw in another class:

public class Dalmatian extends Dog {
    public int spots;
}

Using the Dog(Dog) copy constructor would strip a Dalmatian object of its spots, turning it into a plain-old Dog object. Clearly, this is not desired. This brings us to clone().

clone() Considered Harmful

clone() becomes the obvious solution. clone() will return an exact clone, so a Dalmatian will remain a Dalmatian.

Well, might. clone() doesn't actually guarantee that - it just returns an Object and the documentation is fairly clear that, while it's suggested that it's of the same class as the original, it's not a requirement.

But even assuming it did, there's another problem, involving final objects.

The examples so far have been mutable objects. There's a good reason for this: there's absolutely no reason to clone immutable objects. (And if you've managed to come up with a reason, I don't want to see your code.)

So let's update the Dog class to add a List of names of the dog:

public class Dog implements Cloneable {
    public final List<String> names = new ArrayList<String>();
    public int age;
    public int weight;

    public Dog clone() {
        try {
            return (Dog) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new Error("Is too");
        }
    }
}

The List is final because it only needs to be created at construction time, and may never be null. So let's suppose we want to clone() a Dog:

Dog bowser = new Dog();
bowser.names.add("Fido");
Dog bobBarker = bowser.clone();
bowser.names.add("Bowser");
bobBarker.names.add("Bob Barker");

See the problem? The problem is that clone() is a shallow copy, so the same List<String> is used in both objects. This means that both bowser and bobBarker have three names: Fido, Bowser, and Bob Barker. This wasn't what was intended. So how about:

public class Dog implements Cloneable {
    public final List<String> names = new ArrayList<String>();
    public int age;
    public int weight;

    public Dog clone() {
        try {
            Dog clonedDog = (Dog) super.clone();
            clonedDog.names = new ArrayList<String>(names);
        } catch (CloneNotSupportedException e) {
            throw new Error("Is too");
        }
    }
}

Of course, that won't compile, because it attempts to reassign names, which is final. Oops.

So What's The Right Answer?

Good question. The general answer is that there isn't really one.

I've glossed over other issues with the copy constructor (namely that as members get added and deleted, the copy constructor has to manually be kept in check). However, for final classes, a copy constructor is a fine solution.

For classes that are not final than a solution similar to clone() is a better idea. If clone() is usable directly, great! If it isn't, due to final issues, then something more like the following may work better:

public class Dog {
    public final List<String> names;
    public int age;
    public int weight;

    public Dog() {
        names = new ArrayList<String>();
    }

    protected Dog(Dog original) {
        names = new ArrayList<String>(original.names);
        age = original.age;
        weight = original.weight;
    }

    public Dog copy() {
        return new Dog(this);
    }
}

public class Dalmatian extends Dog {
    public int spots;

    public Dalmatian() { }

    protected Dalmatian(Dalmatian original) {
        super(original);
        spots = original.spots;
    }

    public Dalmatian copy() {
        return new Dalmation(this);
    }
}

Of course, that solution requires every subclass to properly implement both copy() and the copy constructor, which must properly call super(Dog original).

But it solves the copy constructor problem and the clone() problem, although it unfortunately is likely to be slower than clone().

Oh well.

Topics: 

Comments

I am currently enrolled in a university computer science course in which we use Java to program.
Due to confusion and ambiguity in our lecture material, I have been searching the internet extensively for good, clear examples of copy constructor and clone() usage.
I know this is an older post, but it is the first -- and so far, only -- good example I have found. Thank you so much!