我该如何在Java中克隆一个ArrayList并且同时克隆它的元素?
比如我有以下代码:
ArrayList<Dog> dogs = getDogs();
ArrayList<Dog> clonedList = ....something to do with dogs....
我期望clonedList
中的对象与dogs列表中的对象不相同。
我该如何在Java中克隆一个ArrayList并且同时克隆它的元素?
比如我有以下代码:
ArrayList<Dog> dogs = getDogs();
ArrayList<Dog> clonedList = ....something to do with dogs....
我期望clonedList
中的对象与dogs列表中的对象不相同。
个人而言,我会为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接口并使用它。这样,您可以编写通用的克隆方法。
cloneList(List<Object>)
还是 Dog(Object)
? - cdmckay您需要逐个迭代这些项,并将它们克隆,然后在克隆的同时将其放入结果数组中。
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()
方法。
所有标准集合都有拷贝构造函数。请使用它们。
List<Double> original = // some list
List<Double> copy = new ArrayList<Double>(original); //This does a shallow copy
clone()
存在一些设计错误(参见此问题),因此最好避免使用它。
来自《Effective Java 第二版》,第11条: 明智地覆盖clone方法
考虑到与Cloneable相关的所有问题,可以肯定地说,其他接口都不应该扩展它,而专为继承而设计的类(Item 17)也不应该实现它。由于其许多缺点,一些专家程序员选择从不覆盖clone方法,也从不调用它,除了可能要复制数组。如果你为继承而设计一个类,请注意,如果你不提供行为良好的受保护的clone方法,那么子类将无法实现Cloneable。
这本书还描述了拷贝构造函数相对于Cloneable/clone的许多优点。
考虑使用拷贝构造函数的另一个好处:假设您有一个HashSet s
,并且您想将其复制为一个TreeSet
。clone方法无法提供此功能,但使用转换构造函数很容易:new TreeSet(s)
。
Java 8提供了一种新的、优雅而简洁的方法来调用元素dogs的复制构造函数或克隆方法:流、lambda表达式和收集器。
复制构造函数:
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
的收集器上一个例子中的收集器可以制作成一个工具方法。由于这是一个非常常见的操作,我个人喜欢让它变得简短美观。就像这样:
ArrayList<Dog> clonedDogs = dogs.stream().map(d -> d.clone()).collect(toArrayList());
public static <T> Collector<T, ?, ArrayList<T>> toArrayList() {
return Collectors.toCollection(ArrayList::new);
}
CloneNotSupportedException
的注意事项:为了使此解决方案有效,Dog
类的clone
方法不能声明它抛出CloneNotSupportedException
异常。原因是map
方法的参数不允许抛出任何已检查的异常。
示例代码如下:
// Note: Method is public and returns Dog, not Object
@Override
public Dog clone() /* Note: No throws clause here */ { ...
clonedDogs.add
被多个线程同时调用。使用collect
的版本是线程安全的。这是流库函数模型的优点之一,相同的代码可以用于并行流。 - Liidogs
中的任何元素中的更改,反之在克隆列表中的更改将反映在两个列表中。而且,正如其他人指出的那样,我认为复制构造函数是创建新副本的最佳选项。所以,我们的lambda表达式将是 List<Dog> clonedList = dogs.stream().map(Dog::new).collect(Collectors.toList());
,我在 Dog
类中有复制构造函数。不管怎样,谢谢。 - Yogen Rai基本上有三种方法可以避免手动迭代,
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 使用带有 int
参数的 addAll(int index, Collection<? extends E> c)
方法
ArrayList<Dog> dogs = getDogs();
ArrayList<Dog> clonedList = new ArrayList<Dog>();
clonedList.addAll(0, dogs);
NB:如果在操作正在进行时修改了指定的集合,则这些操作的行为将是未定义的。
我认为当前的绿色答案不好,你可能会问为什么?
我认为序列化的方式也很差,您可能需要在各个地方添加可序列化的标记。
那么解决方案是什么:
Java深度克隆库 这个克隆库 是一个小型的、开源的(Apache许可证)Java库,可以深度克隆对象。对象不必实现Cloneable接口。实际上,这个库可以克隆任何Java对象。例如,在缓存实现中,如果您不希望缓存对象被修改,或者想要创建对象的深层副本时,可以使用它。
Cloner cloner=new Cloner();
XX clone = cloner.deepClone(someObjectOfTypeXX);
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。
使用这种方法的优点是,你可以在不必创建类、接口和克隆逻辑的情况下解决问题(如果你的对象内部还有其他对象列表,则可能会非常冗长)。
我一直使用这个选项:
ArrayList<Dog> clonedList = new ArrayList<Dog>(name_of_arraylist_that_you_need_to_Clone);
ArrayList
(通过迭代它并将每个元素复制到新的 ArrayList
中),因为 clone()
不会为你完成。原因是 ArrayList
中包含的对象本身可能不实现 Clonable
。
编辑:...这正是 Varkhan 的代码所做的。其他一些将ArrayList作为深拷贝复制的替代方法
替代方法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);
现在回到狗的深拷贝。
狗类只有可变字段。
狗类:
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 +
'}';
}
}
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}]
评论:由于在更改列表dogs后,列表clonedDogs没有影响/更改,因此ArrayList的深层复制成功了!
备选方案2 - 不使用外部包的方法:
在Dog类中引入了一个新方法“clone()”,并删除了与备选方案1相比的“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}]
评论: 在更改列表狗后,克隆狗列表没有任何影响/更改, 因此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