【问题标题】:Cloning a List in Java在 Java 中克隆一个列表
【发布时间】:2017-04-11 11:52:15
【问题描述】:

我正在处理对象中的一个函数,该函数将对象列表作为参数并将其内容克隆到自己的列表中。修改新的 List 应该不会影响传入的 List。我知道 List 会通过引用传递,但是列表中的对象是通过引用还是值传递? (对不起,如果这听起来很愚蠢)

我正在传递一个扩展 Piece 类的棋子列表(棋子、车等)。我正在考虑在 Piece 类中创建一个 clonePiece() 函数,但我不知道如何去做。这是我目前所拥有的:

    public void copyPieces(List<Piece> whitePieces, List<Piece> blackPieces){
    for (int i = 0; i < whitePieces.size(); i++){
        this.whitePieces.add(whitePieces.get(i).clonePiece());
    }
    for (int i = 0; i < blackPieces.size(); i++){
        this.whitePieces.add(blackPieces.get(i).clonePiece());
    }

您将如何在一个抽象类中实现一个 clonePiece() 函数来创建其继承类的新实例?

编辑:

public abstract class Piece {
private int color;
private int x;
private int y;

public Piece (int color, int x, int y){
    this.color = color;
    this.y = y;
    this.x = x;
}

public int getColor(){
    return this.color;
}
public int getX(){
    return this.x;
}
public int getY(){
    return this.y;
}

public void move(int x, int y, Board board){
    board.getGameTiles()[this.x][this.y].setToUnoccupied();
    this.x = x;
    this.y = y;
}

public abstract ArrayList<Move> getMoves(Board board);

public Piece clonePiece(){
    return this;
}

}

public class Rook extends Piece{

int x, y, color;
private ArrayList<Move> moves;

public Rook(int color, int x, int y) {
    super(color, x, y);
    this.x = x;
    this.y = y;
    this.color = color;
    moves = new ArrayList<>();
}

@Override
public ArrayList<Move> getMoves(Board board) {

    //moves right
    int a = 1;
    while(UtilFunctions.isInBoundaries(x+a, y)){
        if(!board.getGameTiles()[x+a][y].isTileOccupied()){
            //add move type 0 for passive move
            moves.add(new Move(x, y, x+a, y, 0));
        }
        else{
            if(board.getGameTiles()[x+a][y].getPiece().getColor() != this.color){
                //add move type 1 for attack move
                moves.add(new Move(x, y, x+a, y, 1));
            }
            break;
        }
        a++;
    }

    //moves left
    a = -1;
    while(UtilFunctions.isInBoundaries(x+a, y)){
        if(!board.getGameTiles()[x+a][y].isTileOccupied()){
            //add move type 0 for passive move
            moves.add(new Move(x, y, x+a, y, 0));
        }
        else{
            if(board.getGameTiles()[x+a][y].getPiece().getColor() != this.color){
                //add move type 1 for attack move
                moves.add(new Move(x, y, x+a, y, 1));
            }
            break;
        }
        a++;
    }

    //moves up
    a = 1;
    while(UtilFunctions.isInBoundaries(x, y+a)){
        if(!board.getGameTiles()[x][y+a].isTileOccupied()){
            //add move type 0 for passive move
            moves.add(new Move(x, y, x, y+a, 0));
        }
        else{
            if(board.getGameTiles()[x][y+a].getPiece().getColor() != this.color){
                //add move type 1 for attack move
                moves.add(new Move(x, y, x, y+a, 1));
            }
            break;
        }
        a++;
    }

    //moves down
    a = -1;
    while(UtilFunctions.isInBoundaries(x, y+a)){
        if(!board.getGameTiles()[x][y+a].isTileOccupied()){
            //add move type 0 for passive move
            moves.add(new Move(x, y, x, y+a, 0));
        }
        else{
            if(board.getGameTiles()[x][y+a].getPiece().getColor() != this.color){
                //add move type 1 for attack move
                moves.add(new Move(x, y, x, y+a, 1));
            }
            break;
        }
        a++;
    }

    return moves;
}

}

【问题讨论】:

  • 您也想自己克隆这些片段吗?是不可变的还是它们包含一些可变的状态?
  • 是的,我想自己克隆这些片段并将它们放入新列表中。不确定可变性(我对编程很陌生),但 Pieces 只是扩展 Piece 类的类。 (即骑士、主教)
  • 您能否发布Piece 类的代码以及它的后代之一?

标签: java oop inheritance deep-copy


【解决方案1】:

让片段返回它自己的#copy 方法(或使用Clonable):

public abstract class Piece {

    public Piece copy() {
        //return copy
    }
}

这可以类似地用在子类中:

public class Rook extends Piece {

    @Override
    public Rook copy() {
        //return copy
    }
}

然后,当您拥有Piece 对象列表时,您只需在对象上调用#copy 并替换列表中的引用。例如:

List<Piece> pieces = /* some list */;
List<Piece> copy = new ArrayList<>(pieces);
copy.replaceAll(Piece::copy); //replace references with copies

【讨论】:

  • Piece 类的 copy() 中的代码是什么?如果我在 Piece 类中创建 copy() 抽象并在每个继承的类中覆盖它,它会起作用吗?
  • 当然,这是合理的。这个想法是让您的#copy/#clone 为各个子类创建一个新的适当实例。使其抽象化将强制任何实现子类提供副本。额外的好处是每个子类都可以确保其中的内容(例如列表)被适当地复制。
【解决方案2】:

您的子类不应有任何超出父类 Piece 的字段。在这个级别上没有什么本质上的不同。从子类中删除其他字段,然后授予对父 Piece 类字段的访问权限。

abstract class Piece {

    Piece(boolean white, int x, int y){
        //set fields
        ...
    }

    public abstract Piece copyPiece();
}

然后在所有子类中实现方法copyPiece()。然后调用:

public void copyPieces(List<Piece> whitePieces, List<Piece> blackPieces){
    for (Piece whitePiece : whitePieces){
        this.whitePieces.add(whitePiece.copyPiece());
    }
    for (Piece blackPiece : blackPieces){
        this.blackPieces.add(blackPiece.copyPiece());
    }
}

发布代码后编辑:

由于发布的代码,我的原始答案不适合。相应修改。

我觉得有些事情很奇怪。首先,颜色可以是布尔值white,因为只有两种颜色。接下来,您的 Rook 类无法设置对父 Piece 类的 xy 字段的访问权限似乎很奇怪。

您的 clonePiece 类除了返回自身之外什么都不做,这根本不是克隆。克隆将是:return new Piece(this.color, this.x, this.y);

接下来,您的move(x, y, board) 方法不正确。首先,考虑不要传入board,而是让使用 move 方法的类来操作它。如果您不想走这条路线,则必须在棋盘上设置棋子的新位置,而不仅仅是删除旧位置。

【讨论】:

  • 这行不通,因为他正在使用继承。
  • 对不起,我对编程很陌生,但是您将如何在 Piece 类中复制其后代中的字段?假设我想复制一个继承自 Piece 的主教。 “new Piece(whitePiece)”这一行如何访问bishop中的数据?
  • 你的 Rook 类不应该比 Piece 有任何额外的字段。在场地层面,从 Rook 到 Pawn 并没有本质上的不同。删除子类的所有字段。
  • 感谢您的指点。看来我对面向对象编程的掌握很差。至于我之前的问题,我的意思是问复制构造函数如何知道复制的部分是 Bishop 还是 Rook?如果我要创建一个新的 Piece,它会知道要创建哪个后代类吗?
  • 复制构造函数不知道它是什么部分。它将返回一个类型 Piece。如果孩子中有额外的字段,它不会复制这些额外的字段。
【解决方案3】:

首先,Clonablecommonly recommended to be avoided,因此我建议您改为实现复制构造函数或您自己的复制方法。

这是我建议的实现:

为了从polymorphism中受益,在Piece中添加一个抽象方法来制作deep copies

public abstract Piece deepCopy();

以及子类中的对应实现

@Override
public Rook deepCopy() {
    Rook rook = new Rook(this.getColor(), this.getX(), this.getY());
    for (Move m : this.moves) {
        rock.addMove(new Move(m));
    }
    return rook;
}

colorxy 都是原始类型,这意味着在赋值时,值会被复制。但是,Rook 还包含字段move,它是引用类型Move 的列表。这意味着为了能够进行深层复制,Move 类也需要包含复制构造函数或复制方法。前者在这里显示。

public Move(Move move) {
    this(move.x, move.y);
}

最后,以 Java 7 方式复制您的片段列表:

List<Piece> newList = new ArrayList<>();
for (Piece p : oldList) {
    newList.add(p.deepCopy());
}

或 Java 8 方式(如 Darshan Mehta 之前建议的那样):

List<Piece> newList = whitePieces.stream()
    .map(p -> p.deepCopy())
    .collect(Collectors.toList());

【讨论】:

    【解决方案4】:

    EDIT更新了示例以支持继承

    您可以使Piece 类实现Cloneable 接口并覆盖impl 类中的克隆方法。 Piece 和 Rook 类将如下所示:

    abstract class Piece implements Cloneable{
        int x;
        int y;
    
        protected void copy(Piece p){
            p.x = this.x;
            p.y = this.y;
        }
    }
    
    class Rook extends Piece{
        int z;
    
        @Override
        public Object clone(){
            Rook rook = new Rook();
            super.copy(rook);
            rook.z = this.z;
            return rook;
        }
    }
    

    完成后,您可以在任何片段对象上调用此方法并获取深度克隆的实例。以下是深度克隆片段列表的 Java 8 流示例:

    List<Rook> pieces = new ArrayList<>();
    
    List<Piece> cloned = pieces.stream()
                         .map(p -> (Piece) p.clone())
                         .collect(Collectors.toList());
    
    }
    

    【讨论】:

    • @ToddSewell 更新了答案以支持继承
    • 我会在每个继承自 piece 的类中实现一个 clone() 函数吗?如果是这种情况,我可以在 Piece 对象上调用 .clone() 吗?例如,如果我想克隆一个 List whitePieces,即使在 Piece 中没有定义 clone(),我还能调用 whitePieces.get(0).clone() 吗?
    • 是的,您需要在所有类中实现clone() 方法,因为 Piece 类不知道子类的字段。是的,在 Piece 引用上调用 clone() 实际上会调用 impl 类,具体取决于引用的 instance。这就是继承的工作原理..
    猜你喜欢
    • 2013-01-06
    • 2017-03-26
    • 1970-01-01
    • 1970-01-01
    • 2021-05-21
    • 2011-02-12
    • 2012-02-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多