299

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

例如我有:

ArrayList<Dog> dogs = getDogs();
ArrayList<Dog> clonedList = ....something to do with dogs....

而且我希望其中的对象clonedList与狗列表中的对象不同。

4

21 回答 21

213

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

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()方法。

于 2009-04-03T20:43:23.003 回答
207

我个人会为 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 中搞砸 Cloneable 的东西。它还与您复制 Java 集合的方式相匹配。

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

于 2009-04-03T22:02:18.277 回答
148

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

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 的许多优点。

  • 他们不依赖于容易冒险的语言外对象创建机制
  • 他们不要求强制遵守记录薄弱的约定
  • 它们与正确使用 final 字段不冲突
  • 他们不会抛出不必要的检查异常
  • 他们不需要演员表。

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

于 2012-04-10T04:30:40.290 回答
50

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

复制构造函数:

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

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

克隆方法[1]:

List<Dog> clonedDogs = dogs.stream().map(Dog::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 注意到这一点。

于 2015-11-03T19:36:05.030 回答
29

基本上有三种方式无需手动迭代,

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);

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

于 2015-02-18T13:05:05.160 回答
17

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

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

序列化的方式也很糟糕 imo,您可能必须在所有地方添加 Serializable 。

那么解决方案是什么:

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

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

在https://github.com/kostaskougios/cloning上查看

于 2009-08-07T05:58:58.270 回答
11

您可以使用 JSON(带有 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());

您也可以使用其他 JSON 库(如 Jackson)来实现。

使用这种方法的优点是您可以解决问题而无需创建类、接口和克隆逻辑(如果您的对象内部有其他对象列表,这可能会很长)

于 2016-06-09T21:44:16.040 回答
6

我一直使用这个选项:

ArrayList<Dog> clonedList = new ArrayList<Dog>(name_of_arraylist_that_you_need_to_Clone);
于 2018-05-18T07:20:16.803 回答
2

您将需要ArrayList手动克隆(通过迭代它并将每个元素复制到一个新的ArrayList),因为clone()它不会为您完成。这样做的原因是包含在 中的对象ArrayList可能不会Clonable自己实现。

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

于 2009-04-03T20:46:06.790 回答
2

将 ArrayList 复制为 Deep Copy 的其他一些替代方法

备选方案 1 - 使用外部包 commons-lang3,方法SerializationUtils.clone()

SerializationUtils.clone()

假设我们有一个类 dog,其中类的字段是可变的,并且至少一个字段是 String 类型的对象并且是可变的 - 不是原始数据类型(否则浅拷贝就足够了)。

浅拷贝示例:

List<Dog> dogs = getDogs(); // We assume it returns a list of Dogs
List<Dog> clonedDogs = new ArrayList<>(dogs);

现在回到狗的深拷贝。

Dog 类确实只有可变字段。

狗类:

public class Dog implements Serializable {
    private String name;
    private int age;

    public Dog() {
        // Class with only mutable fields!
        this.name = "NO_NAME";
        this.age = -1;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

请注意,Dog 类实现了 Serializable!这使得可以利用方法“SerializationUtils.clone(dog)”

阅读主要方法中的注释以了解结果。它表明我们已经成功地制作了 ArrayList() 的深拷贝。在上下文中参见下面的“SerializationUtils.clone(dog)”:

public static void main(String[] args) {
    Dog dog1 = new Dog();
    dog1.setName("Buddy");
    dog1.setAge(1);

    Dog dog2 = new Dog();
    dog2.setName("Milo");
    dog2.setAge(2);

    List<Dog> dogs = new ArrayList<>(Arrays.asList(dog1,dog2));

    // Output: 'List dogs: [Dog{name='Buddy', age=1}, Dog{name='Milo', age=2}]'
    System.out.println("List dogs: " + dogs);

    // Let's clone and make a deep copy of the dogs' ArrayList with external package commons-lang3:
    List<Dog> clonedDogs = dogs.stream().map(dog -> SerializationUtils.clone(dog)).collect(Collectors.toList());
    // Output: 'Now list dogs are deep copied into list clonedDogs.'
    System.out.println("Now list dogs are deep copied into list clonedDogs.");

    // A change on dog1 or dog2 can not impact a deep copy.
    // Let's make a change on dog1 and dog2, and test this
    // statement.
    dog1.setName("Bella");
    dog1.setAge(3);
    dog2.setName("Molly");
    dog2.setAge(4);

    // The change is made on list dogs!
    // Output: 'List dogs after change: [Dog{name='Bella', age=3}, Dog{name='Molly', age=4}]'
    System.out.println("List dogs after change: " + dogs);

    // There is no impact on list clonedDogs's inner objects after the deep copy.
    // The deep copy of list clonedDogs was successful!
    // If clonedDogs would be a shallow copy we would see the change on the field
    // "private String name", the change made in list dogs, when setting the names
    // Bella and Molly.
    // Output clonedDogs:
    // 'After change in list dogs, no impact/change in list clonedDogs:\n'
    // '[Dog{name='Buddy', age=1}, Dog{name='Milo', age=2}]\n'
    System.out.println("After change in list dogs, no impact/change in list clonedDogs: \n" + clonedDogs);
}

输出:

List dogs: [Dog{name='Buddy', age=1}, Dog{name='Milo', age=2}]
Now list dogs are deep copied into list clonedDogs.
List dogs after change: [Dog{name='Bella', age=3}, Dog{name='Molly', age=4}]
After change in list dogs, no impact/change in list clonedDogs:
[Dog{name='Buddy', age=1}, Dog{name='Milo', age=2}]

评论: 由于更改list dogs后对list clonedDogs没有影响/更改,那么ArrayList的深拷贝就成功了!

备选方案 2 - 不使用外部软件包:

与备选方案 1 相比,在 Dog 类中引入了新方法“clone()”,并删除了“implements Serializable”。

clone()

狗类:

public class Dog {
    private String name;
    private int age;

    public Dog() {
        // Class with only mutable fields!
        this.name = "NO_NAME";
        this.age = -1;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    /**
     * Returns a deep copy of the Dog
     * @return new instance of {@link Dog}
     */
    public Dog clone() {
        Dog newDog = new Dog();
        newDog.setName(this.name);
        newDog.setAge(this.age);
        return newDog;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

阅读下面主要方法中的注释以了解结果。它表明我们已经成功地制作了 ArrayList() 的深拷贝。在上下文中参见下面的“clone()”方法:

public static void main(String[] args) {
    Dog dog1 = new Dog();
    dog1.setName("Buddy");
    dog1.setAge(1);

    Dog dog2 = new Dog();
    dog2.setName("Milo");
    dog2.setAge(2);

    List<Dog> dogs = new ArrayList<>(Arrays.asList(dog1,dog2));

    // Output: 'List dogs: [Dog{name='Buddy', age=1}, Dog{name='Milo', age=2}]'
    System.out.println("List dogs: " + dogs);

    // Let's clone and make a deep copy of the dogs' ArrayList:
    List<Dog> clonedDogs = dogs.stream().map(dog -> dog.clone()).collect(Collectors.toList());
    // Output: 'Now list dogs are deep copied into list clonedDogs.'
    System.out.println("Now list dogs are deep copied into list clonedDogs.");

    // A change on dog1 or dog2 can not impact a deep copy.
    // Let's make a change on dog1 and dog2, and test this
    // statement.
    dog1.setName("Bella");
    dog1.setAge(3);
    dog2.setName("Molly");
    dog2.setAge(4);

    // The change is made on list dogs!
    // Output: 'List dogs after change: [Dog{name='Bella', age=3}, Dog{name='Molly', age=4}]'
    System.out.println("List dogs after change: " + dogs);

    // There is no impact on list clonedDogs's inner objects after the deep copy.
    // The deep copy of list clonedDogs was successful!
    // If clonedDogs would be a shallow copy we would see the change on the field
    // "private String name", the change made in list dogs, when setting the names
    // Bella and Molly.
    // Output clonedDogs:
    // 'After change in list dogs, no impact/change in list clonedDogs:\n'
    // '[Dog{name='Buddy', age=1}, Dog{name='Milo', age=2}]\n'
    System.out.println("After change in list dogs, no impact/change in list clonedDogs: \n" + clonedDogs);
}

输出:

List dogs: [Dog{name='Buddy', age=1}, Dog{name='Milo', age=2}]
Now list dogs are deep copied into list clonedDogs.
List dogs after change: [Dog{name='Bella', age=3}, Dog{name='Molly', age=4}]
After change in list dogs, no impact/change in list clonedDogs:
[Dog{name='Buddy', age=1}, Dog{name='Milo', age=2}]

评论: 由于更改list dogs后对list clonedDogs没有影响/更改,那么ArrayList的深拷贝就成功了!

注意 1: 备选方案 1 比备选方案 2 慢得多,但更易于维护,因为您不需要更新任何方法,例如 clone()。

注意 2:对于替代 1,以下 maven 依赖项用于方法“SerializationUtils.clone()”:

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.9</version>
</dependency>

在以下位置查找更多 common-lang3 版本:

https://mvnrepository.com/artifact/org.apache.commons/commons-lang3

于 2020-08-18T21:06:56.657 回答
1

为您的对象覆盖 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()....

于 2012-09-10T13:45:30.377 回答
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<>();
    }
}
于 2015-10-01T20:59:54.027 回答
1
List<Dog> dogs;
List<Dog> copiedDogs = dogs.stream().map(dog -> SerializationUtils.clone(dog)).Collectors.toList());

这将深度复制每只狗

于 2019-11-27T14:34:13.517 回答
0

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

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

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

于 2009-04-04T10:12:21.843 回答
0

这是使用通用模板类型的解决方案:

public static <T> List<T> copyList(List<T> source) {
    List<T> dest = new ArrayList<T>();
    for (T item : source) { dest.add(item); }
    return dest;
}
于 2012-03-20T23:31:25.787 回答
0

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

链接下载 commons-lang-2.3.jar

如何使用

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

我希望这个可以有所帮助。

:D

于 2013-04-04T16:20:19.507 回答
0

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

有一种方法SerializationUtils.clone(Object);

例子

this.myObjectCloned = SerializationUtils.clone(this.object);
于 2013-05-02T19:25:00.657 回答
0

我刚刚开发了一个能够克隆实体对象和 java.util.List 对象的库。只需在https://drive.google.com/open?id=0B69Sui5ah93EUTloSktFUkctN0U下载 jar并使用静态方法 cloneListObject(List list)。此方法不仅克隆 List,还克隆所有实体元素。

于 2017-05-11T23:28:53.923 回答
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() 创建的。因此对新列表所做的任何更改都不会影响原始列表

于 2019-11-07T09:48:15.560 回答
0

简单的方法是

ArrayList<Dog> dogs = getDogs();
ArrayList<Dog> clonedList = new ArrayList<Dog>(dogs);
于 2020-08-12T17:41:40.060 回答
-2

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

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

让我知道它是否不适合你。

于 2016-11-23T22:24:09.230 回答