【问题标题】:Deep cloning an object of a class in java在java中深度克隆类的对象
【发布时间】:2017-02-23 04:04:57
【问题描述】:

我的主类中有一个我的 Game.java 类的对象游戏。我需要创建一个游戏对象的副本(game_copy),这样当我对 game_copy 板进行更改时,游戏板不会被更改。我将这个 game_copy 对象设为:

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

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

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

【问题讨论】:

  • Java deep copy library的可能重复
  • 有深拷贝和浅拷贝的概念。您似乎想要进行深层复制并创建一个新对象。我建议您编写自定义代码来创建一个新游戏对象并遍历旧游戏对象并将您想要持久保存到新游戏对象中的所有状态变量一一复制。是的,这需要更多的工作和代码,但这是获得您想要的东西的最佳方式。
  • @mba12 我已经添加了我在登陆克隆方法之前使用的复制构造函数。那也没用。
  • 为什么你需要克隆一个Game
  • 我正在为游戏制作人工智能。所以我必须在板上使用极小极大搜索。为了实现这一点,我需要一个重复的游戏板,这样我现在的状态板就不会改变。

标签: java deep-copy


【解决方案1】:

根据 Javadocs:

否则,此方法会创建此对象的类的新实例,并使用此对象的相应字段的内容来初始化其所有字段,就像通过赋值一样;字段的内容本身不会被克隆。因此,此方法执行此对象的“浅拷贝”,而不是“深拷贝”操作。

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

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

【讨论】:

  • 我也尝试为 rplayer.java 类制作副本,但也没有成功。
  • 那么您可能没有正确执行此操作。没有办法使用new 并且手动设置变量不起作用。正如其他人已经提到的,您的另一个选择是序列化对象。
【解决方案2】:

我发现通过使用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;
}

}

【讨论】:

  • 当我按照您的建议尝试使用 makeClone() 方法制作副本时,我获得的 Game game_copy = new Game() ; game_copy = game.makeClone() ; game_copy 为空。
  • 请提供您的代码。我已经测试了我的代码。有用。还有一件事,你不需要这样做Game game_copy = new Game() ;makeClone 方法返回一个新的 Game 对象。
  • Here 是我测试过的代码。看看并尝试找出您的代码不同的地方。并运行将生成两种不同类型对象的代码,正如您将在控制台中看到的那样。
  • 感谢代码! :) 但是由于我的 Game 类也使用了我定义的 rplayer 类的数组,它在 makeClone() 方法中给了我NotSerializableException rplayer。如何深入使用这两个类?
  • 你还需要通过实现rplayer接口来标记Serializable
【解决方案3】:

一般不推荐实现Cloneable接口。 Joshua Bloch 的Effective Java (2nd Edition) 的一些摘录:

明智地覆盖clone。 Cloneable 接口旨在作为一个 mixin 接口 让对象宣传它们允许克隆。不幸的是,它无法达到这个目的。它的主要缺陷是缺少clone 方法,而Object 的克隆方法受到保护。

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

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

这里的想法是你想完全消除任何“远处的幽灵行动”;也就是说,确保不可能通过间接更改Game B 的状态来更改Game A 的状态。如果某些东西是不可变的(字符串、包装的原语、您自己的不可变类),则可以重用引用它们之间。否则,您需要将这些对象的状态复制到您的副本将使用的全新实例。

【讨论】:

  • 感谢您的回答! :) 但这正是我在想到克隆的想法之前所做的,因为这对我不起作用。我复制的对象和原始对象都共享相同的引用,并且在其中任何一个更改时都更改了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-09-09
  • 2023-04-01
  • 2012-06-05
  • 2012-06-26
  • 2013-09-06
相关资源
最近更新 更多