使用 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 中是原生的?

Object.clone() 的逐个字段复制是啥?

为什么Object.clone()是Java原生的?

JavaScript中的对象复制(Object Clone)

Java Object Clone