【问题标题】:deep copy of two-dim array containing objects in java not working as expectedjava中包含对象的二维数组的深层副本未按预期工作
【发布时间】:2013-12-24 21:19:09
【问题描述】:

这是对以前帖子的重写。我现在写了一个完整的新项目,只是为了展示我不理解的行为。下面你会看到代码。该问题在本文末尾的测试输出中变得可见。我的假设是错误位于标记为 /* PROBLEMATIC */ 的行,但我不知道这会是什么错误。

我想做的是这样的:

  • 创建一个板对象boardA

  • 创建另一个名为 boardB,它是 boardA 的深层副本,因此它在其数组 pos[][] 中引用不同的 Square 对象(正如输出告诉我的那样,确实如此)。

因此,当我输出对象 id(使用 hashCode())(如在第二个 Board 复制构造函数中)时,我希望为每个对象(我确实得到)获得不同的哈希值,并且为父对象获得不同的哈希值每个 Square 对象(我不这样做)。我希望输出澄清了这一点。

有什么想法吗? 谢谢。

这是完整的测试代码:

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

这是一个示例输出:

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

如您所见,initpos[0][0] 和 this.pos[0][0] 行的 gh 值相同 (1874519541)。 我希望 this.pos[0][0] 的 gh-value 为 998786479,即 boardB 的 id。

所以克隆的 Square 对象(它应该有不同的 id)仍然认为它属于第一个 Board 对象。但是,既然我将它分配给构造函数中的新 Board 对象,那怎么可能呢?

【问题讨论】:

    标签: java arrays clone


    【解决方案1】:

    Java 的 clone() 很棘手(如果不是邪恶的话),内部类更是如此。

    我认为this 适用:

    “正确”的方式是定义内部的clone方法 带有封闭类参数的类。

    或者更好的是,忘记克隆并编写一个复制构造函数。

    【讨论】:

    【解决方案2】:

    SquareBoard 的内部类。内部类的每个实例都有对其外部类实例的隐式引用。 Board.this 是你的情况。如果没有外部实例,这样的对象也无法存在。

    因此,内部类的对象属于外部类对象的一个​​实例。并且它们属于哪个实例不会改变。甚至,尤其是如果您克隆它们,因为克隆会克隆所有内容。

    不要使用 .clone() 从新实例中创建手动副本。

    最简单的方法是提供一个拷贝构造函数。那是一个构造函数,它接受一个它自己类型的对象。这样你也不需要泄露实现细节(你的 main 方法需要知道有一个隐藏在板中的数组)。

    public class CloneTest {
        public static void main(String[] args) {
            Board boardA = new Board();
            Board boardB = new Board(boardA);
        }
    }
    
    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);
                }
        }
    
        /** A copy constructor */
        public Board(Board other) {
            pos = new Square[boardSize][boardSize];
            for (int row = 0; row < boardSize; row++)
                for (int col = 0; col < boardSize; col++) {
                    this.pos[row][col] = new Square(other.pos[row][col]);
                }
        }
    
        class Square {
            int row;
            int col;
    
            /** A Copy Constructor */
            public Square(Square other) {
                row = other.row;
                col = other.col;
            }
    
            public Square(int r, int c) {
                row = r;
                col = c;
            }
    
            public int gameHash() {
                return Board.this.hashCode();
            }
        }
    }
    

    【讨论】:

    • zapl,我仍然不太明白为什么我的代码不起作用,但你的代码可以!谢谢。所以克隆它在 Java 中似乎是一件坏事;-)
    • @jackthehipster 您在代码中克隆了Board.this 的含义。所以return Board.this.hashCode() 不会改变它的值。
    • 好的,我想我现在明白了……克隆将为与克隆对象相同的父对象生成一个新的子对象,对吗?我认为它会简单地创建一个具有相同属性的全新对象,并通过从 boardB 对象中引用它,它会“接受” boardB 作为其父对象(听起来很傻):) 。所以对父对象的引用实际上是被克隆的对象的一些隐藏属性,不是吗?
    • @jackthehipster 正确。父对象属性也确实被克隆了。
    • 一个后续问题:在我的真实程序中(不是上面的示例代码),Square 只是可能的类之一。游戏中每个可能的棋子都有一个子类。所以我需要在不知道它属于哪个类的情况下复制每个方块。有没有办法做类似的事情: this.pos[row][col] = new (class of other.pos[row][col])(other.pos[row][col]); ?
    猜你喜欢
    • 2019-10-11
    • 1970-01-01
    • 1970-01-01
    • 2012-01-21
    • 2011-12-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-06
    相关资源
    最近更新 更多