深度克隆 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) ,您可以重新使用它们之间的引用。否则,您需要将这些对象的状态复制到您的副本将使用的全新 个实例。
我的 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 aclone
method, andObject
'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) ,您可以重新使用它们之间的引用。否则,您需要将这些对象的状态复制到您的副本将使用的全新 个实例。