clone()方法:我认为ArrayList.clone()方法执行的是浅拷贝

23
ArrayList<Integer> a=new ArrayList<Integer>();
a.add(5);
ArrayList<Integer> b=(ArrayList<Integer>)a.clone();
a.add(6);
System.out.println(b.toString());
在上面的代码片段中,我认为clone()执行的是浅复制。因此,ba应该指向同一块内存地址。然而,当我执行b.toString()时,答案只有5。如果clone()执行的是浅复制,为什么6也没有被显示出来呢?

关于浅拷贝的同样问题:我期望它复制对象类型字段的引用和基本类型字段的值。https://dev59.com/Wm455IYBdhLWcg3wAvNI#4592545 这个答案很有帮助,避免使用clone()。现在我放手让它自行处理何时保持与复制时相同的内容,何时会随原始数据的更新而自动更新。 - Woooody Amadeus
6个回答

55

浅拷贝并不意味着它们指向相同的内存位置。那只是一个赋值操作:List b = a;

克隆创建了一个新的实例,持有相同的元素。这意味着您有两个不同的列表,但它们的内容是相同的。如果您更改第一个列表内部对象的状态,它也将在第二个列表中发生更改。(由于您使用了不可变类型 - Integer - 您无法观察到此效果)。

然而,您应该考虑不要使用clone()。它可以在集合中正常工作,但通常被认为是有缺陷的。请使用复制构造函数 - new ArrayList(originalList)


1
那么深拷贝和浅拷贝有什么区别呢? - TimeToCodeTheRoad
1
创建一个仅包含可变类对象的列表的克隆,是否是浪费的? - TimeToCodeTheRoad
1
@TimeToCodeTheRoad 浅拷贝的工作方式就像Bozho所解释的那样,深拷贝会复制ArrayList及其元素。 - João Portela
2
仅仅是对@JoãoPortela所说的进行一点补充,深拷贝会导致所有对象被复制。你会得到一个新的容器对象,里面的所有对象也都被复制了。但需要注意的是,那些包含在集合中的对象可能不会被复制。你需要确保每个对象都正确地实现了深拷贝。如果做得正确,你就可以从N个对象变成2N个对象。 - thecoshman
这是两个不同的列表,但第二个列表会跟随第一个列表的变化?哎呀,这真让人困惑。 - Lendl Leyba
1
@LeiLeyba 不是同一个列表,但底层对象将保持不变。这意味着如果我从列表a中检索一些对象o并对其进行修改,则它将在所有其他引用它的地方(包括列表b)中发生更改。这并不意味着当我从列表a中删除对对象o的引用时,它也会从列表b中删除。 - Insomniac

7
如果它像你想的那样,那么clone方法将会完全无用,因为在这种情况下,以下行将是等效的:
ArrayList<Integer> b = (ArrayList<Integer>)a.clone();
ArrayList<Integer> b = a;

克隆是一种创建具有完全相同属性(在克隆操作时)的两个实体的过程,就像在现实世界中一样。

正如Bozho所提到的 - 避免使用Java的clone()概念。即使是它的作者也提到了它是有缺陷的。

这个问题及其答案非常有价值,并提供了链接到Josh Bloch对他自己作品的评论;-)


2

这确实进行了浅复制,下面是来自ArrayList源代码的clone方法的注释:

返回此ArrayList实例的浅表副本。(元素本身不会被复制。)

为了理解这一点,让我们看一下ArrayList中clone方法的片段。

v.elementData = Arrays.copyOf(elementData, size);

我们知道,当我们将一个对象分配给变量时,JAVA不会创建一个全新的该对象的副本。相反,这个变量成为另一个指向原始对象的引用。
因此,elementData实际上存储着放入这个ArrayList中的对象的引用。而克隆只是复制这些引用,没有创建任何对象的副本。
当然,您可以从克隆的ArrayList中删除或添加新的引用。
然而,在一个ArrayList中修改旧对象将影响到原始ArrayList。由于Integer是不可变的,很难通过您的示例进行说明。
为了看到副作用,您可以定义一个自定义的可变对象。
class Person {
        private int a;

        public void setA(int a) {
            this.a = a;
        }
        public int getA() {
            return a;
        }
        @Override
        public String toString() {
            return String.valueOf(a);
        } 
   } 

那么您可以使用以下代码来进行测试。
        Person p1 = new Person();
        Person p2 = new Person();

        ArrayList<Person> tt = new ArrayList<Person>();
        tt.add(p1);
        tt.add(p2);

        ArrayList<Person> yy = (ArrayList<Person>) tt.clone();
        Person vv = yy.get(yy.indexOf(p2));
        vv.setA(12);
        yy.remove(p1);

        System.out.println("tt: " + tt);
        System.out.println("yy: " +yy);

输出结果应该是:

tt: [0, 12]
yy: [12]

看到副作用了吗?我们只改变了yy中的元素,但它也反映在tt中。


1

浅拷贝是由你所提到的Object.clone()提供的默认克隆策略。对象类的clone()方法创建一个新实例,并将Cloneable对象的所有字段复制到该新实例中(无论它是基本类型还是引用类型)。因此,在引用类型的情况下,只有引用位被复制到新实例中,因此,两个对象的引用变量将指向同一个对象。上面我们看到的示例就是浅拷贝的一个例子。

深拷贝顾名思义,意味着从一个对象复制到另一个对象的所有内容。为了实现这一点,我们需要欺骗clone()方法,以提供自己的克隆策略。我们可以通过实现Cloneable接口并在我们对象层次结构中的每个引用类型中覆盖clone()方法,然后在我们对象的clone方法中调用super.clone()和这些clone()方法来实现。但是,如果您查看ArrayList的源代码中的clone()方法,您会发现它在调用super.clone()之后外部复制了v.elementData = Arrays.copyOf(elementData, size);,这意味着ArrayList的clone()深度复制其内容。

public Object clone() {
    try {
        ArrayList<?> v = (ArrayList<?>) super.clone();
        v.elementData = Arrays.copyOf(elementData, size);
        v.modCount = 0;
        return v;
    } catch (CloneNotSupportedException e) {
        // this shouldn't happen, since we are Cloneable
        throw new InternalError(e);
    }
}

如果您想了解更多关于克隆及其类型(如深度克隆和浅层克隆)的信息,请阅读Java Cloning and Types of Cloning (Shallow and Deep) in Details with Example


0
ArrayList中的克隆函数与将一个ArrayList复制到另一个ArrayList并不相同。如果我们使用clone(),它将保存原始ArrayList的副本,但是如果我们在使用clone()之后对原始ArrayList进行任何更改,则不会影响已复制的ArrayList。
例如:
public static void main(String[] a) {

List list = new ArrayList();

list.add("A");

List list2 = ((List) ((ArrayList) list).clone());

System.out.println(list);
System.out.println(list2);

list.clear();

System.out.println(list);
System.out.println(list2);
}

输出:-

[A]

[A]

[ ]

[A]


0

我们不能动态地选择想要添加字符串的位置吗?像这样

int r=k.nextInt();
Integer i6=new Integer(r);
System.out.println("Enter the address");
String p6=k.nextLine();
ar3.add(i6,p6);

读取整数后没有执行后续操作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接