深度克隆 java 中 class 的对象

Deep cloning an object of a class in java

我的 Game.java class 在我的主 class 中有一个对象游戏。我需要创建游戏对象的副本 (game_copy),这样当我更改 game_copy 的棋盘时,游戏棋盘不会改变。我将这个 game_copy 对象设为:

Game game_copy = new Game() ;
game_copy = game.clone() ;

但是当我对 game_copy 的棋盘进行更改时,游戏的棋盘也会发生变化,因为它们仍然共享一个参考。我该如何解决这个问题? 这是我正在使用的游戏和 rplayer classes:

class Game implements Cloneable{
     int n;
     ArrayList<ArrayList<String>> board;
     rplayer [] players;
     int a ;

    public Game(int n){
      this.n=n;
      players = new rplayer[2] ;
      players[0]=new rplayer(this.a);
      players[1]=new rplayer(this.a);
      board= new ArrayList<ArrayList<String>>();
       for(int p= 0 ; p < n*n ; p++){
        ArrayList<String> r = new ArrayList<String>() ;
        board.add(r) ;
       }
     }

   public Game clone(){ 
    Game gm ;
    try { 
        gm =  (Game) super.clone(); 
    } 
    catch (CloneNotSupportedException e) { 
        System.out.println (" Cloning not allowed. " );
         return null; 
    }
    return gm
 }
}

class rplayer{
    int a;
    public rplayer(int a){
      this.a=a;
    }
}

这是我在尝试使用 .clone() 方法之前尝试的方法,但是制作复制构造函数的想法也没有奏效。这两个对象总是相关的。

public Game(Game g){
this.n=g.n;
this.players = new rplayer[2] ;
rplayer[] temp_players = new rplayer[2] ;
temp_players = g.players;
this.players = temp_players ;
this.board= new ArrayList<ArrayList<String>>();
ArrayList<ArrayList<String>> temp_board = new ArrayList<ArrayList<String>>() ;
    for(int p= 0 ; p < n*n ; p++){
        ArrayList<String> r = new ArrayList<String>() ;
        board.add(r) ;
        temp_board.add(r) ;
    }
    temp_board = g.board ;
    board= temp_board;
}

根据 Javadocs:

Otherwise, this method creates a new instance of the class of this object and initializes all its fields with exactly the contents of the corresponding fields of this object, as if by assignment; the contents of the fields are not themselves cloned. Thus, this method performs a "shallow copy" of this object, not a "deep copy" operation.

换句话说,创建了一个新对象,但对象内部的引用仍然指向相同的成员变量。您需要复制对象内部的内容以及对象本身。

https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html

我发现通过使用 Serializable 接口标记对象然后在内存中序列化对象来深度复制复杂对象。由于您的 Game 对象引用了另一个对象,并且该对象也引用了其他对象,因此最好通过序列化克隆该对象。这将确保您将获得完全不同的对象。 Java 克隆将无法达到目的,因为对象变得越来越复杂。

这里是如何在内存中序列化对象的示例代码。

class Game implements Serializable {

........
........
........

public Game makeClone() throws IOException, ClassNotFoundException {
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    ObjectOutputStream out = new ObjectOutputStream(outputStream);
    out.writeObject(this);

    ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
    ObjectInputStream in = new ObjectInputStream(inputStream);
    Game copied = (Game) in.readObject();
    return copied;
}

}

通常不建议实现 Cloneable 接口。 Joshua Bloch 的一些摘录 Effective Java (2nd Edition)

Override clone judiciously. The Cloneable interface was intended as a mixin interface for objects to advertise that they permit cloning. Unfortunately, it fails to serve this purpose. Its primary flaw is that it lacks a clone method, and Object's clone method is protected.

Josh 接着解释了 Cloneable 接口和 clone 方法的许多缺点。作为替代方案,他建议您创建一个 "copy constructor" 将一个对象的状态复制到您的新副本中,防御性地复制需要保护的内容。在您的情况下,复制构造函数可能看起来像这样...

public Game(Game original) {
    if (null == original) 
        throw new IllegalArgumentException("Game argument must not be null for copying");

    // int primitives can be copied by value harmlessly.
    this.n = original.n;
    this.a = original.a;

    // Collections require special protection, however, to ensure that
    // manipulating one game's state doesn't manipulate the other.
    ArrayList<ArrayList<String>> boardCopy = new ArrayList<>();
    for (ArrayList<String> subList : original.board) {
        // Fortunately, Strings are immutable, so we don't need to
        // manually copy them. And ArrayList has a copy constructor
        // of its own, so that's handy!
        ArrayList<String> subCopy = new ArrayList<>(subList); 
        boardCopy.add(subCopy);
    }
    this.board = boardCopy;

    this.players = new rplayer[original.players.length];
    for (int i = 0; i < original.players.length; i++) {
        rplayer player = original.players[i];
        // If--and ONLY if--the rplayer class is immutable, this
        // is safe; otherwise you need to copy each player on your
        // own!
        this.players[i] = player;
    }
}

这里的想法是你想完全消除任何"spooky action at a distance";也就是说,确保不可能通过间接更改 Game B 的状态来更改 Game A 的状态。如果某些东西是不可变的(字符串、包装基元、您自己的不可变 class) ,您可以重新使用它们之间的引用。否则,您需要将这些对象的状态复制到您的副本将使用的全新 个实例。