使用 Object.clone() 的错误
Posted
技术标签:
【中文标题】使用 Object.clone() 的错误【英文标题】:Bug in using Object.clone() 【发布时间】:2012-04-01 23:47:16 【问题描述】:我有下一个场景:
我在我的主类中定义了一个int[][]
变量。 int[][] matrix1 = new int[10][10]
我给它一些值。然后我调用一个方法并将这个变量作为参数发送给该方法。作为它发送的对象是通过引用而不是通过值,所以在方法内部,因为我必须更改 matrix1 包含的值但在对象从方法返回后不影响它,所以我像这样克隆它:
private void myMethod( int[][] matrix1 )
int[][] matrix1Clone = matrix1.clone();
//And next i do some changes to matrix1Clone
......
但问题是我对matrix1Clone 所做的更改也发生在matrix1 中。所以它并没有真正创建 matrix1 对象的克隆,但两个变量都指向同一个对象。
这是为什么?我似乎无法弄清楚。为什么克隆方法不起作用?
如果您需要更多信息,请询问。不过恐怕就这些了,真的不能给你更多,但也许我可以试试。
我可能遗漏了什么,但我不知道是什么...
谢谢。
编辑
抱歉,打错字了。已经很晚了,我累了。我确实在使用克隆方法,这就是为什么我很困惑,因为它不起作用:(。
【问题讨论】:
Java 中的多维数组是指向数组的指针数组。 Clone 只会克隆第一个数组。 那么我该如何实现呢?我是否需要创建一个循环将原始 int 从一个数组复制到另一个数组? 您需要创建一个循环来克隆内部数组。或者 Arrays 类中可能有一个函数可以做到这一点——自从它被发明以来,我还没有真正研究过它。 【参考方案1】:实际上,数组没有值,只有指向对象或原始数据类型的指针。如果你想要详细的答案,你应该在这里阅读我的评论:Java is NEVER pass-by-reference, right?...right? 或这里:In Java, what is a shallow copy?
那么,由于数组是指针,如果你克隆一个带有指针的指针会发生什么?起初,指针是真实复制的,但这些指针仅指向未克隆的其他对象。所以如果你想克隆,我建议不要使用数组,而是使用“更难”的数据结构:类。另一种可能性是永远不要在数组中存储数组......就像我只将数组用于容器!
但我无法向您提供有关 Java 多维泛型的详细信息,因为我从不处理它们,不仅因为它们可能不一致,因为它们是数组(它们无论如何都违反了一些 OO 原则并使代码看起来很丑)。
编辑
我正在运行一些测试 clone 方法如何用于类中的数组,问题是什么以及我们有哪些解决方法。
首先是测试数据结构:
public class Foobar implements Cloneable
String[] array;
public Foobar()
this.array = new String[10];
public String getValue()
return array[0];
public String[] getArray()
return array;
public void setArray(String[] array)
this.array = array;
@Override
public Object clone()
try
Foobar foobar = (Foobar) super.clone();
foobar.setArray(array);
return foobar;
catch(Exception e)
return null;
现在是控制器:
String[] array = new String[10];
array[0] = "111";
Foobar foo1 = new Foobar();
foo1.setArray(array);
Foobar foo2 = foo1; //Alternation: Foobar foo2 = (Foobar) foo1.clone();
System.out.println("Instance: "+foo1.getArray()+" with value: "+foo1.getValue());
System.out.println("Instance: "+foo2.getArray()+" with value: "+foo2.getValue());
array[0] = "999";
System.out.println("Instance: "+foo1.getArray()+" with value: "+foo1.getValue());
System.out.println("Instance: "+foo2.getArray()+" with value: "+foo2.getValue());
测试结果总是这样——无论我使用 = 还是 clone():
Instance: [Ljava.lang.String;@42e816 with value: 111
Instance: [Ljava.lang.String;@42e816 with value: 111
Instance: [Ljava.lang.String;@42e816 with value: 999
Instance: [Ljava.lang.String;@42e816 with value: 999
这不好好!!
那么解决方法是什么?我建议在每个数据结构类中都这样做:
public class Foobar implements Serializable
//any class variables...it doesn't matter which!
public Foobar()
//do initialisation here...it doesn't matter what you do!
public Foobar copy()
try
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
Foobar foobar = (Foobar) ois.readObject();
return foobar;
catch(Exception e)
return null;
因此,您只需实现一行代码即可获得完整副本:
Foobar foo2 = foo1.copy(); //nice and easy!!
此解决方案的优点:通常实现接口 Serializable 以使类“可复制”就足够了。如果没有,您可以通过阅读 Serializable Javadoc 中的内容来解决任何问题!
更重要的是:无论您想要“可复制”类中的哪种对象都没有关系,因此您无需在此问题上花费更多时间。毕竟,上面的代码是深度嵌入 Java 以来最简单、最快的解决方案,并且只使用 RAM! (感谢 ByteArrayOutputStream)
享受吧!
更新:请注意,如果您想要一个临时堆栈或者您正在处理线程(通常:如果您需要让对象完全相互独立,那么您只需要使用对象的副本) )。否则你根本不应该复制任何东西!此外,如果您将一些数据写入文件或套接字,则不需要副本。甚至我建议仅在真正使用时才实现复制方法:对于数据结构(模型)。因此,使用这种强大的方法时要小心(否则它可能会减慢您的应用程序,甚至如果您无缘无故地制作数百万份副本,甚至会填满 Java VM 存储,这确实会导致堆栈溢出:o)。
编辑
我在这个问题上做了更多的工作。因为我突然发现,Java API 中没有“原始”数组的公共 clone() 方法! (来自 SUN 的“复活节彩蛋”,用于 String[] 或 int[] 等数组;-)
由于我使用真正的数组作为 Foobar 的基本数据结构(不是 ArrayLists!),我可以像这样更改(上述类的)克隆方法:
@Override
public Object clone()
try
Foobar foobar = (Foobar) super.clone();
String[] arrayClone = array.clone(); //who thought that this is possible?!
foobar.setArray(arrayClone);
return foobar;
catch(Exception e)
return null;
现在我们可以直接得到这个结果:
Instance: [Ljava.lang.String;@42e816 with value: 111
Instance: [Ljava.lang.String;@9304b1 with value: 111
Instance: [Ljava.lang.String;@42e816 with value: 999
Instance: [Ljava.lang.String;@9304b1 with value: 111
“双嵌套” 对象解决了问题!!!如您所见,克隆具有独立于原始对象的不同对象...因此 foo1.equals(foo2)) 将是错误的!
解决方案:在一个类的clone方法中,你也需要克隆它所有的类变量!(但是如果某些类变量是ArrayLists或者多维数组,即使这个解决方案也行不通'不行!)
最后,真正的问题是什么? ArrayList 类没有克隆它的数组,它只调用了 Array 类中的方法 copyOf,这是有害的。 所以永远不要使用 ArrayList 类的 clone 方法,也永远不要从 ArrayList 继承任何类,因为它的 clone 方法不起作用!(只有当 ArrayList 类只包含基元而没有对象时它才有效.. .否则只需使用上面简单的 ByteArray 解决方案!)。
请注意,对于像 Object[][] 这样的多维数组,您总是需要实现上面的 ByteArray 解决方案,它们不能被克隆!如果您的阵列很大,则可能需要一段时间并且还需要一些 RAM。
现在您是克隆专家了! :-D
【讨论】:
【参考方案2】:您给matrix1Clone
提供与matrix1
相同的引用。如果您更改matrix1Clone
,那么matrix1
也会更改。
您可以通过迭代源数组来复制您的数组:
public static int[][] clone2DArray(int[][] array)
int rows = array.length;
//clone the 'shallow' structure of array
int[][] newArray = array.clone();
//clone the 'deep' structure of array
for(int row = 0; row < rows; row++)
newArray[row] = array[row].clone();
return newArray;
【讨论】:
对不起,打错了....请在编辑后立即检查。我按照你们说的做了……只是我累了,我打错了。对不起 太棒了....还有一种方法...好像我确实受够了:)..谢谢。我会在一分钟内查看它。 您不需要转换array.clone()
和array[row].clone()
的结果。他们已经是正确的类型【参考方案3】:
尝试使用 clone() http://docs.oracle.com/javase/1.4.2/docs/api/java/lang/Object.html#clone%28%29 克隆它
private void myMethod( int[][] matrix1 )
int[][] matrix1Clone = matrix1.clone();
或者,使用循环复制所有值
编辑:克隆()的 Api 表示它应该返回对象的副本,但行为可能会有所不同,具体取决于克隆的对象。尝试迭代数组作为替代方案。因为它是一个二维数组,所以你需要一个嵌套循环:
for(int i=0; i<old.length; i++)
for(int j=0; j<old[i].length; j++)
old[i][j]=copy[i][j];
其中 old 是“原始数组”,copy 是副本
【讨论】:
对不起,打错了....请在编辑后立即检查。我按照你说的做了……只是写错了:)累了打哈欠 忘了说我编辑了代码。应该使用这个 是的...我知道,只是我不确定为什么它不会克隆它。谢谢。以上是关于使用 Object.clone() 的错误的主要内容,如果未能解决你的问题,请参考以下文章
Fabric.js stage.getActiveObject().clone() 与 fabric.util.object.clone(stage.getActiveObject())
为啥 Object.clone() 在 Java 中是原生的?