Search code examples
javaarraysclone

deep copy of two-dim array containing objects in java not working as expected


This is a rewrite of a former post. I now wrote a complete new project just to show the behaviour I don't understand. Below you see the code. The problem becomes visible in the test output that is at the end of this post. My assumption is that the error is located at the line marked /* PROBLEMATIC */ but I don't know how that would be wrong.

What I want to do is this:

  • create a board object boardA

  • create another one called boardB, which is a deep copy of boardA, so it refers to different Square objects in its array pos[][] (which, as the output tells me, it indeed does).

So when I output the object ids (using hashCode()) (like in the second Board copy constructor), I expect to get different hash values for each object (which I indeed get) and also different hash values for the parent of each Square object (which I DON'T). The output clarifies this, I hope.

Any ideas? Thanks.

Here's the complete test code:

public class CloneTest {

    public static void main(String[] args) {
        Board boardA = new Board();
        Board boardB = new Board(boardA.pos);

        System.out.println("boardA=" + boardA.hashCode());
        System.out.println("boardB=" + boardB.hashCode());
    }

}

class Board {
    final int boardSize = 2;
    Square[][] pos;

    /* create empty board */
    public Board() {
        pos = new Square[boardSize][boardSize];
        for(int row=0; row < boardSize; row++)
            for(int col=0;col < boardSize; col++) {
                pos[row][col] = new Square(row,col);
            }
    }

    /* copy existing board */
    public Board(Square[][] initpos) {
        pos = new Square[boardSize][boardSize];

        for(int row=0; row < boardSize; row++)
            for(int col=0;col < boardSize; col++) {

                System.out.println("initpos["+row+"]["+col+"]=" + initpos[row][col].hashCode() + 
                        " gh=" + initpos[row][col].gameHash());

                try {
                    this.pos[row][col] = initpos[row][col].clone(); /* PROBLEMATIC? */
                } catch (CloneNotSupportedException e) {
                }

                System.out.println("this.pos["+row+"]["+col+"]=" +  this.pos[row][col].hashCode() + 
                        " gh=" + this.pos[row][col].gameHash());

            }
    }

    class Square implements Cloneable {
        int row;
        int col;

        public Square(int r, int c) {
            row = r;
            col = c;
        }

        public Square clone() throws CloneNotSupportedException {
            Square newsquare = (Square) super.clone(); /* WRONG?*/
            return newsquare;
        }

        public int gameHash() {
            return Board.this.hashCode();
        }
    }
}

Here's a sample output:

initpos[0][0]=1598553873 gh=1874519541
this.pos[0][0]=1464824097 gh=1874519541
initpos[0][1]=546069071 gh=1874519541
this.pos[0][1]=1585252666 gh=1874519541
initpos[1][0]=1659432780 gh=1874519541
this.pos[1][0]=716609871 gh=1874519541
initpos[1][1]=973809521 gh=1874519541
this.pos[1][1]=843745660 gh=1874519541
boardA=1874519541
boardB=998786479

As you can see, the gh-values is the same (1874519541) for the initpos[0][0] and the this.pos[0][0] lines. I would expect the gh-value of this.pos[0][0] to be 998786479, which is the id of boardB.

So the cloned Square object (which has a different id as it should have) still thinks it belongs to the first Board object. But how can that be since I assign it to the new Board object inside the constructor??


Solution

  • Java's clone() is tricky (if not evil), and more so with inner classes.

    I think this applies:

    The "proper" way is to define the clone method of the inner class with an argument of enclosing class.

    Or better, forget about clone and code a copy constructor.