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

pal*_*lig 258 java collections clone deep-copy

如何克隆ArrayList并在Java中克隆其项目?

例如,我有:

ArrayList<Dog> dogs = getDogs();
ArrayList<Dog> clonedList = ....something to do with dogs....
Run Code Online (Sandbox Code Playgroud)

我希望那些物品clonedList与狗列表中的物品不同.

Var*_*han 192

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

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;
}
Run Code Online (Sandbox Code Playgroud)

为了实现这一点,显然,您必须让您的Dog类实现Cloneable接口并覆盖该clone()方法.

  • 但是,你不能一般地做到这一点.clone()不是Cloneable接口的一部分. (19认同)
  • 但是clone()在Object中受到保护,因此您无法访问它.尝试编译该代码. (13认同)
  • 所有类都扩展了Object,因此它们可以覆盖clone().这就是Cloneable的用途! (5认同)
  • 我说,创建一个工厂或构建器,甚至只是一个静态方法,它将获取Dog的实例,并手动将字段复制到新实例,并返回该新实例. (3认同)
  • 这是一个很好的答案.Cloneable实际上是一个接口.但是,mmyers有一点,因为clone()方法是Object类中声明的受保护方法.您必须在Dog类中重写此方法,并自己手动复制字段. (2认同)
  • 我建议永远不要使用名为“clone”的函数。始终使用名为“deepClone”或“shallowClone”的函数。 (2认同)

cdm*_*kay 190

我个人会给Dog添加一个构造函数:

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

    public Dog(Dog dog) {
        // Copy all the fields of Dog.
    }
}
Run Code Online (Sandbox Code Playgroud)

然后迭代(如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;
}
Run Code Online (Sandbox Code Playgroud)

我发现这样做的好处是你不需要在Java中使用破解的Cloneable东西.它还与您复制Java集合的方式相匹配.

另一种选择可能是编写自己的ICloneable接口并使用它.这样你就可以编写一个通用的克隆方法.

  • +1复制构造函数是要走的路.也像ICloneable接口的想法. (11认同)

Ros*_*one 139

所有标准集合都有拷贝构造函数.使用它们.

List<Double> original = // some list
List<Double> copy = new ArrayList<Double>(original); //This does a shallow copy
Run Code Online (Sandbox Code Playgroud)

clone()设计有几个错误(见这个问题),所以最好避免它.

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

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

本书还介绍了复制构造函数相对于Cloneable/clone的许多优点.

  • 他们不依赖于易于冒险的语言外对象创建机制
  • 他们并不要求无法强制遵守精简文件的惯例
  • 它们与正确使用最终字段不冲突
  • 它们不会抛出不必要的已检查异常
  • 他们不需要演员阵容.

考虑使用复制构造函数的另一个好处:假设您有一个HashSet s,并且您希望将其复制为TreeSet.克隆方法无法提供此功能,但转换构造函数很容易:new TreeSet(s).

  • 就我所知,标准集合的副本构造函数创建了*浅*副本,而不是*深*副本.这里提出的问题寻找一个深刻的副本答案. (78认同)
  • 这完全错误,复制构造做了一个浅层复制 - 问题的唯一问题 (17认同)
  • 这个答案的正确之处在于,如果您没有改变列表中的对象,添加或删除项目不会将它们从两个列表中删除。它不像简单的赋值那样*浅。 (2认同)

Lii*_*Lii 38

Java 8提供了一种新方法,可以优雅紧凑地调用元素狗上的复制构造函数或克隆方法:Streams,lambdascollectors.

复制构造函数:

List<Dog> clonedDogs = dogs.stream().map(Dog::new).collect(toList());
Run Code Online (Sandbox Code Playgroud)

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

克隆方法[1]:

List<Dog> clonedDogs = dogs.stream().map(d -> d.clone()).collect(toList());
Run Code Online (Sandbox Code Playgroud)

得到一个ArrayList结果

或者,如果你需要ArrayList退回(如果你想稍后修改它):

ArrayList<Dog> clonedDogs = dogs.stream().map(Dog::new).collect(toCollection(ArrayList::new));
Run Code Online (Sandbox Code Playgroud)

更新列表

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

dogs.replaceAll(Dog::new);
Run Code Online (Sandbox Code Playgroud)

所有例子都假设import static java.util.stream.Collectors.*;.


收藏家的ArrayLists

可以将最后一个示例中的收集器制成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);
}
Run Code Online (Sandbox Code Playgroud)

[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 */ { ...
Run Code Online (Sandbox Code Playgroud)

然而,这应该不是一个大问题,因为无论如何这是最好的做法.(例如,Effectice Java提供了这个建议.)

感谢Gustavo注意到这一点.


PS:

如果你发现它更漂亮,你可以使用方法引用语法来完成同样的事情:

List<Dog> clonedDogs = dogs.stream().map(Dog::clone).collect(toList());
Run Code Online (Sandbox Code Playgroud)


jav*_*tar 27

基本上有三种方法没有手动迭代,

1使用构造函数

ArrayList<Dog> dogs = getDogs();
ArrayList<Dog> clonedList = new ArrayList<Dog>(dogs);
Run Code Online (Sandbox Code Playgroud)

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

ArrayList<Dog> dogs = getDogs();
ArrayList<Dog> clonedList = new ArrayList<Dog>();
clonedList.addAll(dogs);
Run Code Online (Sandbox Code Playgroud)

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

ArrayList<Dog> dogs = getDogs();
ArrayList<Dog> clonedList = new ArrayList<Dog>();
clonedList.addAll(0, dogs);
Run Code Online (Sandbox Code Playgroud)

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

  • 请注意,并非所有这三种变体都只创建列表的*浅拷贝* (47认同)
  • 这不是深度克隆,这两个列表保留相同的对象,只复制引用但只有Dog对象,一旦修改了列表,下一个列表将有相同的更改.不要这么多赞成. (8认同)
  • @Neeson.Z 所有方法都会创建列表的深层副本和列表元素的浅层副本。如果您修改列表中的一个元素,则其他列表将反映更改,但如果您修改其中一个列表(例如删除对象),则另一个列表将保持不变。 (2认同)

Coj*_*nes 18

我认为目前的绿色答案很糟糕,为什么你会问?

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

序列化的方式也很糟糕,你可能需要在整个地方添加Serializable.

那么解决方案是什么:

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

Cloner cloner=new Cloner();
XX clone = cloner.deepClone(someObjectOfTypeXX);
Run Code Online (Sandbox Code Playgroud)

请访问https://github.com/kostaskougios/cloning查看

  • 这种方法的一个警告是它使用反射,这可能比Varkhan的解决方案慢得多. (7认同)
  • 我不明白第一点"它需要大量的代码".您正在谈论的库需要更多代码.它只是你放置它的地方的问题.否则我同意一个特殊的图书馆这种事情有帮助.. (6认同)

sag*_*its 7

我找到了一种方法,您可以使用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());
Run Code Online (Sandbox Code Playgroud)

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


bes*_*rtm 5

我一直使用此选项:

ArrayList<Dog> clonedList = new ArrayList<Dog>(name_of_arraylist_that_you_need_to_Clone);
Run Code Online (Sandbox Code Playgroud)

  • 嗯,我不知道为什么没人支持它,但是它有效。谢谢哥们儿! (2认同)