使用Object.clone()时出错

时间:2012-03-16 12:24:34

标签: java clone

我有下一个场景:

我在我的主类中定义了一个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对象的克隆,但两个变量都指向同一个对象。

这是为什么?我似乎无法弄明白。为什么克隆方法不起作用?

如果您需要更多信息,请询问。但我担心这是关于它的,不能真的给你更多,但也许我可以试试。

我可能会遗漏一些东西,但我无法弄清楚是什么......

感谢。

修改

抱歉,打错了。这是晚了,我累了。我确实使用克隆方法,这就是为什么我感到困惑,因为它不起作用:(。

3 个答案:

答案 0 :(得分:2)

您提供matrix1Clonematrix1相同的引用。如果您更改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;
    }

答案 1 :(得分:1)

尝试使用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();
}

或者,使用循环

复制所有值

编辑:针对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是“原始数组”,副本是副本

答案 2 :(得分:0)

实际上,数组没有值,而是指向对象或原始数据类型的指针。如果您需要详细的答案,请在此处阅读我的评论:Java is NEVER pass-by-reference, right?...right?或此处:In Java, what is a shallow copy?

因此,由于数组是指针,如果克隆带有指针的指针会发生什么?首先,指针被复制为真实,但这些指针仅指向未克隆的其他对象。因此,如果你想克隆,我建议不要使用数组,而是使用“更难”的数据结构:类。另一种可能性是永远不会将数组存储在数组中......就像我只将数组用于容器一样!

但我无法向您提供有关Java多维泛型的详细信息,因为我从不处理它们,不仅因为它们可能存在不一致性,因为它们是数组(它们无论如何都违反了一些OO原则并使代码看起来很难看)。

修改

我正在运行一些测试,克隆方法如何适用于类中的数组,问题是什么以及我们有哪些变通方法。

首先是测试数据结构:

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))将为false!

解决方案:在类的clone方法中,您还需要克隆其所有类变量!(但如果某些类变量是ArrayLists或更多维数组,那么即使这个解决方案也胜出'工作!)

最后,真正的问题是什么? ArrayList类没有克隆它的数组,它只调用类Array中的方法copyOf,这是有害的。 所以永远不要使用类ArrayList的clone方法,也不要从ArrayList继承任何类,因为它的clone方法不起作用!(仅当类ArrayList只包含基元而没有对象时才有效。 。或者只使用上面简单的ByteArray解决方案!)。

请注意,对于像[Object [] []这样的更多维数组,您总是需要实现上面的ByteArray解决方案,它们无法克隆!如果您的阵列很大,可能需要一段时间并且需要一些RAM。

现在你是克隆专家! :-D