如何克隆ArrayList并克隆其内容?


Answers:


199

您将需要迭代这些项目,并逐个克隆它们,然后将克隆放入结果数组中。

public static List<Dog> cloneList(List<Dog> list) {
    List<Dog> clone = new ArrayList<Dog>(list.size());
    for (Dog item : list) clone.add(item.clone());
    return clone;
}

显然,要使该方法起作用,您将必须让您的Dog类来实现Cloneable接口并重写该clone()方法。


19
但是,您不能一般地这样做。clone()不是Cloneable接口的一部分。
迈克尔·迈尔斯

13
但是clone()在对象中受保护,因此您无法访问它。尝试编译该代码。
迈克尔·迈尔斯

5
所有类都扩展Object,因此它们可以覆盖clone()。这就是Cloneable的目的!
09年

2
这是一个很好的答案。可克隆实际上是一个接口。但是,mmyers有一点,因为clone()方法是在Object类中声明的受保护方法。您必须在Dog类中重写此方法,然后自己手动复制字段。
何塞

3
我说,创建一个工厂或构建器,甚至只是一个静态方法,它将使用Dog的一个实例,并手动将字段复制到一个新实例,然后返回该新实例。
何塞

196

我个人将为Dog添加一个构造函数:

class Dog
{
    public Dog()
    { ... } // Regular constructor

    public Dog(Dog dog) {
        // Copy all the fields of Dog.
    }
}

然后进行迭代(如Varkhan的答案所示):

public static List<Dog> cloneList(List<Dog> dogList) {
    List<Dog> clonedList = new ArrayList<Dog>(dogList.size());
    for (Dog dog : dogList) {
        clonedList.add(new Dog(dog));
    }
    return clonedList;
}

我发现这样做的好处是您无需费心处理Java中损坏的可克隆内容。它还与您复制Java集合的方式匹配。

另一种选择是编写自己的ICloneable接口并使用它。这样,您可以编写通用的克隆方法。


您可以复制DOG的所有字段来更具体吗?我真的不明白:(
aNdRO博士2014年

是否可以为未定义的对象(而不是Dog)编写该函数?
Tobi G.

@TobiG。我不明白你的意思。你要cloneList(List<Object>)还是Dog(Object)
cdmckay

@cdmckay一个函数,适用于cloneList(List <Object>),cloneList(List <Dog>)和cloneList(List <Cat>)。但是我猜你不能称呼一个通用的构造函数...?
Tobi G.

@TobiG。像一般的克隆功能呢?这个问题不是真的。
cdmckay

143

所有标准集合都有副本构造函数。使用它们。

List<Double> original = // some list
List<Double> copy = new ArrayList<Double>(original); //This does a shallow copy

clone()设计时有几个错误(请参见此问题),因此最好避免这种情况。

摘自Effective Java 2nd Edition,第11条:明智地覆盖克隆

鉴于与Cloneable相关的所有问题,可以肯定地说其他接口不应该扩展它,而为继承而设计的类(第17项)也不应实现它。由于它的许多缺点,一些专业的程序员只是选择永不覆盖clone方法,也不要调用它,除非复制数组。如果设计一个用于继承的类,请注意,如果选择不提供行为良好的受保护的克隆方法,则子类将无法实现Cloneable。

本书还描述了复制构造函数相对于Cloneable / clone的许多优点。

  • 他们不依赖于容易产生风险的语言外对象创建机制
  • 他们不要求强制执行严格记录在案的约定
  • 它们与正确使用最终字段不冲突
  • 他们不会抛出不必要的检查异常
  • 他们不需要演员。

考虑使用复制构造函数的另一个好处:假设您有一个HashSet s,并且想要将其复制为一个TreeSet。clone方法无法提供此功能,但是使用转化构造函数即可轻松实现new TreeSet(s)


84
据我所知,标准集合的副本构造函数创建浅表副本,而不是深表副本。此处提出的问题寻找深层答案。
阿卜杜勒2012年

19
这完全是错误的,复制构造者做了一个浅表复制-问题的全部内容
NimChimpsky 2013年

1
关于此答案的正确之处在于,如果您不对列表中的对象进行变异,则添加或删除项目不会将其从两个列表中删除。它不像简单的任务那么浅薄。
Noumenon

42

Java 8提供了一种新的方法来优雅而紧凑地调用元素狗上的copy构造函数或clone方法:Streamslambdascollectors

复制构造函数:

List<Dog> clonedDogs = dogs.stream().map(Dog::new).collect(toList());

该表达式Dog::new称为方法引用。它创建一个函数对象,该对象调用一个构造函数,该构造函数Dog将另一只狗作为参数。

克隆方法[1]:

List<Dog> clonedDogs = dogs.stream().map(d -> d.clone()).collect(toList());

得到ArrayList结果

或者,如果您必须ArrayList退货(以防以后要修改):

ArrayList<Dog> clonedDogs = dogs.stream().map(Dog::new).collect(toCollection(ArrayList::new));

到位更新列表

如果您不需要保留dogs列表的原始内容,则可以使用replaceAll方法并就地更新列表:

dogs.replaceAll(Dog::new);

所有示例均假设import static java.util.stream.Collectors.*;


收藏家的ArrayList小号

上一个示例中的收集器可以做成util方法。由于这是一件很常见的事情,因此我个人希望它短而漂亮。像这样:

ArrayList<Dog> clonedDogs = dogs.stream().map(d -> d.clone()).collect(toArrayList());

public static <T> Collector<T, ?, ArrayList<T>> toArrayList() {
    return Collectors.toCollection(ArrayList::new);
}

[1]注意CloneNotSupportedException

为了使该解决方案起作用,的clone方法Dog 一定不要声明它抛出了CloneNotSupportedException。原因是to的参数map不允许引发任何检查的异常。

像这样:

    // Note: Method is public and returns Dog, not Object
    @Override
    public Dog clone() /* Note: No throws clause here */ { ...

但是,这应该不是一个大问题,因为无论如何这都是最佳实践。(例如,Effectice Java提供了此建议。)

感谢Gustavo注意到这一点。


PS:

如果您觉得它更漂亮,则可以改用方法引用语法来做相同的事情:

List<Dog> clonedDogs = dogs.stream().map(Dog::clone).collect(toList());

在Dog(d)是复制构造函数的地方,您看到这种方法会对性能产生任何影响吗? List<Dog> clonedDogs = new ArrayList<>(); dogs.stream().parallel().forEach(d -> clonedDogs.add(new Dog(d)));
SaurabhJinturkar '16

1
@SaurabhJinturkar:您的版本不是线程安全的,不应与并行流一起使用。这是因为该parallel调用使clonedDogs.addget可以同时从多个线程被调用。使用的版本collect是线程安全的。这是流库功能模型的优势之一,相同的代码可用于并行流。
Lii 2016年

1
@SaurabhJinturkar:而且,收集操作很快。它的功能几乎与您的版本相同,但也适用于并行流。您可以通过使用例如并发队列而不是数组列表来修复版本,但是我几乎可以肯定这会慢很多。
Lii 2016年

每当我尝试使用您的解决方案,我得到一个Unhandled exception type CloneNotSupportedExceptiond.clone()。声明异常或捕获它并不能解决它。
古斯塔沃

@Gustavo:几乎可以肯定是因为您要克隆的对象(Dog在此示例中)不支持克隆。您确定它实现了Clonable接口吗?
Lii

28

基本上,有三种方法无需手动进行迭代,

1使用构造函数

ArrayList<Dog> dogs = getDogs();
ArrayList<Dog> clonedList = new ArrayList<Dog>(dogs);

2使用 addAll(Collection<? extends E> c)

ArrayList<Dog> dogs = getDogs();
ArrayList<Dog> clonedList = new ArrayList<Dog>();
clonedList.addAll(dogs);

3 addAll(int index, Collection<? extends E> c)int参数的使用方法

ArrayList<Dog> dogs = getDogs();
ArrayList<Dog> clonedList = new ArrayList<Dog>();
clonedList.addAll(0, dogs);

注意:如果在操作进行过程中修改了指定的集合,则这些操作的行为将不确定。


49
请勿所有这些3的变型,只有创建浅拷贝的名单
electrobabe

8
这不是深度克隆,这两个列表保留相同的对象,只复制了引用,但是只复制了Dog对象,一旦修改了两个列表,下一个列表将具有相同的更改。不要多投票。
Saorikido

1
@ Neeson.Z所有方法都会创建列表的深层副本和列表元素的浅层副本。如果修改列表的元素,则更改将反映在另一个列表中,但是如果修改列表中的一个(例如,删除对象),则另一个列表将保持不变。
亚历山德罗·特鲁齐

17

我认为当前的绿色答案很不好,为什么您会问?

  • 可能需要添加很多代码
  • 它要求您列出所有要复制的列表并执行此操作

imo序列化的方式也很糟糕,您可能不得不在各处添加Serializable。

那么解决方案是什么:

Java深度 克隆库克隆库是一个小型的,开源的(Apache许可证)Java库,用于深度克隆对象。对象不必实现Cloneable接口。实际上,该库可以克隆任何java对象。如果您不希望修改缓存的对象,或者想要创建对象的深层副本,则可以在缓存实现中使用它。

Cloner cloner=new Cloner();
XX clone = cloner.deepClone(someObjectOfTypeXX);

https://github.com/kostaskougios/cloning中查看


8
这种方法的一个警告是它使用反射,这比Varkhan的解决方案要慢很多。
cdmckay 2010年

6
我不明白第一点“它需要很多代码”。您正在谈论的库将需要更多代码。这只是您放置它的位置的问题。否则,我同意为此类问题提供一个特殊的库会有所帮助..
nawfal

8

我找到了一种方法,您可以使用json对列表进行序列化/反序列化。未序列化时,序列化列表不包含对原始对象的引用。

使用gson:

List<CategoryModel> originalList = new ArrayList<>(); // add some items later
String listAsJson = gson.toJson(originalList);
List<CategoryModel> newList = new Gson().fromJson(listAsJson, new TypeToken<List<CategoryModel>>() {}.getType());

您也可以使用jackson和其他任何json库来实现。


1
我不知道为什么人们不赞成这个答案。其他答案必须实现clone()或更改其依赖性以包括新库。但是JSon库中的大多数项目都已经包含在内。我为此投票。
Satish

1
@Satish是的,这是唯一对我有所帮助的答案,我不确定其他人怎么了,但是无论我做了什么,克隆或使用副本构造函数,我的原始列表都曾经被用来更新,但这种方式不会,所以谢谢作者!
Parag Pawar

好吧,这确实不是出于知识的考虑而纯粹是Java的答复,而是一种快速解决此问题的有效解决方案
marcRDZ

伟大的黑客,配发节省时间的
MXML

6

我一直使用此选项:

ArrayList<Dog> clonedList = new ArrayList<Dog>(name_of_arraylist_that_you_need_to_Clone);

2

您将需要ArrayList手动克隆(通过对其进行迭代并将每个元素复制到一个new ArrayList),因为clone()这样做不会帮您。原因是包含在中的对象ArrayList可能无法实现Clonable

编辑:...,这正是Varkhan的代码所做的。


1
即使它们这样做,除了反射之外,没有其他方法可以访问clone(),而且也不能保证一定成功。
迈克尔·迈尔斯

1

讨厌的方法是反射。这样的事情对我有用。

public static <T extends Cloneable> List<T> deepCloneList(List<T> original) {
    if (original == null || original.size() < 1) {
        return new ArrayList<>();
    }

    try {
        int originalSize = original.size();
        Method cloneMethod = original.get(0).getClass().getDeclaredMethod("clone");
        List<T> clonedList = new ArrayList<>();

        // noinspection ForLoopReplaceableByForEach
        for (int i = 0; i < originalSize; i++) {
            // noinspection unchecked
            clonedList.add((T) cloneMethod.invoke(original.get(i)));
        }
        return clonedList;
    } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
        System.err.println("Couldn't clone list due to " + e.getMessage());
        return new ArrayList<>();
    }
}

整洁又讨厌的把戏!一个潜在的问题:如果original包含不同类的对象,cloneMethod.invoke当使用错误类型的对象调用该对象时,我认为它将失败并发生异常。因此,最好Method为每个对象检索一个特定的克隆。或在其上使用clone方法Object(但由于该方法受到保护,在更多情况下可能会失败)。
Lii

另外,我认为最好将一个运行时异常抛出该捕获子句中,而不是返回一个空列表。
Lii

1
List<Dog> dogs;
List<Dog> copiedDogs = dogs.stream().map(dog -> SerializationUtils.clone(dog)).Collectors.toList());

这将深深复制每只狗


0

其他张贴者是正确的:您需要迭代列表并复制到新列表中。

但是...如果列表中的对象是不可变的,则无需克隆它们。如果您的对象具有复杂的对象图-它们也将必须不可变。

不变性的另一个好处是它们也是线程安全的。



0

为您的对象覆盖clone()方法

class You_class {

    int a;

    @Override
    public You_class clone() {
        You_class you_class = new You_class();
        you_class.a = this.a;
        return you_class;
    }
}

并为Vector obj或ArraiList obj ...调用.clone()。


0

使用commons-lang-2.3.jar该Java库克隆列表的简单方法

链接下载commons-lang-2.3.jar

如何使用

oldList.........
List<YourObject> newList = new ArrayList<YourObject>();
foreach(YourObject obj : oldList){
   newList.add((YourObject)SerializationUtils.clone(obj));
}

希望这一点对您有所帮助。

:D


1
只是一个注释:为什么下议院郎这样的旧版本?请在此处查看发布历史记录:commons.apache.org/proper/commons-lang/release-history.html
informatik01

0

包装 import org.apache.commons.lang.SerializationUtils;

有办法 SerializationUtils.clone(Object);

this.myObjectCloned = SerializationUtils.clone(this.object);

回答这个问题有点过时了。还有很多其他答案,都在问题下方的注释中。
moskito-x


0

下面为我​​工作。

在Dog.java中

public Class Dog{

private String a,b;

public Dog(){} //no args constructor

public Dog(Dog d){ // copy constructor
   this.a=d.a;
   this.b=d.b;
}

}

 -------------------------

 private List<Dog> createCopy(List<Dog> dogs) {
 List<Dog> newDogsList= new ArrayList<>();
 if (CollectionUtils.isNotEmpty(dogs)) {
 dogs.stream().forEach(dog-> newDogsList.add((Dog) SerializationUtils.clone(dog)));
 }
 return newDogsList;
 }

在这里,通过createCopy方法创建的新列表是通过SerializationUtils.clone()创建的。因此,对新列表所做的任何更改都不会影响原始列表


-1

我认为我找到了制作深拷贝ArrayList的一种非常简单的方法。假设您要复制一个String ArrayList arrayA。

ArrayList<String>arrayB = new ArrayList<String>();
arrayB.addAll(arrayA);

让我知道这是否不适合您。


3
如果您使用List <List <JsonObject >>例如,在我的情况下
不起作用

字符串是不可变的。克隆没有意义,在您的示例中,arrayB和arrayA具有相同的对象引用-浅表副本。
Christian Fries
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.