Duplicating objects

Duplicating objects #

In some scenarios, it may be useful to duplicate an object.

In Java, as we saw earlier, copying the value of a variable with reference type does not copy the object that it references. For instance, the following program outputs “yellow”:

Unicorn unicorn = new Unicorn("green");
Unicorn theSameUnicorn = unicorn;

unicorn.color = "yellow";
System.out.println(theSameUnicorn.color);

The need to duplicate objects may notably appear in a program that relies on concurrency.

In our game, the “view” component is in charge of rendering the game visually.

This component exposes a method called drawSnapshot that takes as input a game snapshot and draws this snapshot on screen. The backend calls this method whenever a snapshot must be drawn. However, the backend may send these snapshots faster than they can be displayed (due to the duration of on-screen animations).

To deal with this scenario, the “view” buffers the snapshots that it receives (displaying a new snapshot only after all previously submitted ones have been displayed). For the sake of this exercise, you can think of this buffer as an array with type Snapshot[].

Now consider the following method deleteUnit(int x, int y), executed by our backend each time it receives the instruction to delete a unit:

public class Backend implements EventHandler {

    Snapshot currentSnapshot;

    ...

    public void deleteUnit(int x, int y){

        // if there is a unit on the tile
        if(currentSnapshot.getBoard().getUnit(x,y).isPresent()){

            removeUnitFromTile(x, y);
            drawSnapshot(currentSnapshot);

            shiftUnitsInColumn(x);
            drawSnapshot(currentSnapshot);

            performUnitMerges();
            drawSnapshot(currentSnapshot);

            ...
        }
    }
}

where the auxiliary methods removeUnitFromTile, shiftUnitsInColumn and performUnitMerges may modify the object currentSnapshot.

Observe that the method deleteUnit calls drawSnapshot three times.

Now let us assume that the buffer of the “view” is nonempty when this method is executed, so that the 3 snapshots are added to the buffer, before any of them can be rendered on screen.

How many of these 3 snapshots will be drawn on screen?

Only the third snapshot will be drawn (three times), because the buffer contains three references to the same object.

Shallow, deep and hybrid copy #

Copying a reference to an object can be viewed as the shallowest possible form of copy. As we saw already, this is what happens in Java when a variable with reference type is passed as argument.

At the other end of the spectrum is a so-called deep copy, where all attributes of the copied object are duplicated, recursively. In this case, the copy or any object that it references (recursively) can be modified without affecting their original counterparts.

Between these two extremes:

  • a shallow copy creates a new object (with fresh attributes), but does not copy referenced objects recursively,
  • a hybrid copy is anything between a shallow copy and a deep copy.

in Java #

Copy constructor #

A copy can be performed in Java with a so-called copy constructor. This is an additional constructor that takes an instance of the class as input, and returns a copy of this instance.

For example:

public class Hero {

    int health;

    // standard constructor
    public Hero(){
        this.health = 10;
    }

    // copy constructor
    public Hero(Hero original){
        this.health = original.health;
    }
}

In order to perform a deep(er) copy, a copy constructor may call another copy constructor. For example:

public class Snapshot {

    Hero firstHero;
    Hero secondHero;
    Board board;
    int remainingActions;

    // standard constructor
    public Snapshot(Hero firstHero, Hero secondHero, Board board,
                    int remainingActions){
        // some code here
        ...
    }

    // copy constructor
    public Snapshot(Snapshot original){
        this.firstHero = new Hero(original.firstHero);
        this.secondHero = new Hero(original.secondHero);
        this.board = new Board(original.board);
        this.remainingActions = original.remainingActions;
    }
}

Warning. Similarly to what we saw with the methods toString or equals, beware of recursive implementations of copy constructors, if your program can create an object that refers to itself (directly or indirectly).

The method clone #

Java also provides a native method called clone to duplicate objects. Like the methods toString and equals that we saw earlier, clone is an instance method of the native Java class Object, which is an (implicit) superclass of every other class.

Overriding clone can be more concise than using a copy constructor (especially for a class with a large number of attributes).

Warning. Overriding clone can be error-prone, especially in presence of a class hierarchy (see Effective Java, Item 13 for further insight). Notably, the overriding class must implement the Cloneable interface, and an implementation of clone usually calls super.clone recursively (even for a class whose only superclass is Object). For these reasons, overriding clone is sometimes discouraged, in favor of conceptually simpler solutions (like copy constructors).