如何复制 Java 集合列表

我有一个 ArrayList,我想完全复制它。我在可能的情况下使用实用程序类,前提是有人花了一些时间来纠正它。所以很自然地,我最终使用了包含一个 copy 方法的 Collections类。

假设我有以下内容:

List<String> a = new ArrayList<String>();
a.add("a");
a.add("b");
a.add("c");
List<String> b = new ArrayList<String>(a.size());


Collections.copy(b,a);

这失败了,因为基本上它认为 b不够大,不能容纳 a。是的,我知道 b的尺寸是0,但是它现在应该足够大了,不是吗?如果我必须先填充 b,那么 Collections.copy()在我的脑海中就变成了一个完全无用的函数。那么,除了编写一个复制函数(我现在就要这么做)之外,还有什么正确的方法可以做到这一点呢?

272202 次浏览

只要做:

List a = new ArrayList();
a.add("a");
a.add("b");
a.add("c");
List b = new ArrayList(a);

ArrayList 有一个构造函数,它将接受另一个 Collection 从中复制元素

b容量为3,而 尺寸为0。ArrayList具有某种缓冲区容量的事实是一个实现细节——它不是 List接口的一部分,因此 Collections.copy(List, List)不使用它。这将是丑陋的特殊情况下 ArrayList

正如 tddMonkey 所指出的,在本例中使用接受集合的 ArrayList 构造函数是一种方法。

对于更复杂的场景(其中可能包括您的实际代码) ,您可能会发现 番石榴中的集合非常有用。

复制 List 的最简单方法是将其传递给新 List 的构造函数:

List<String> b = new ArrayList<>(a);

b将是 a的浅拷贝

看看 Collections.copy(List,List)的源代码(我以前从未见过) ,它似乎是用来按索引处理元素索引的。使用 List.set(int,E),因此元素0将在目标列表中写入元素0,等等。我不得不承认,从 javadocs 来看不是很清楚。

List<String> a = new ArrayList<>(a);
a.add("foo");
b.add("bar");


List<String> b = new ArrayList<>(a); // shallow copy 'a'


// the following will all hold
assert a.get(0) == b.get(0);
assert a.get(1) == b.get(1);
assert a.equals(b);
assert a != b; // 'a' is not the same object as 'b'

如果要复制数组列表,请使用:

List b = new ArrayList();
b.add("aa");
b.add("bb");


List a = new ArrayList(b);
List b = new ArrayList(a.size())

不能设定大小。它设置初始容量(在需要调整大小之前它可以容纳多少个元素)。在这种情况下,一种更简单的复制方式是:

List b = new ArrayList(a);

如果您想象用例将一些值复制到现有集合中,那么复制并非毫无用处。也就是说,你想覆盖现有的元素,而不是插入。

例如: a = [1,2,3,4,5] b = [2,2,2,2,2,3,3,3,3,3,4,4,] a.copy (b) = [1,2,3,4,5,3,3,3,3,3,4,4]

但是,我希望复制方法为源和目标集合的开始索引接受额外的参数,并为 count 接受一个参数。

请参见 Java BUG 6350752

呼叫

List<String> b = new ArrayList<String>(a);

b中创建 a的浅拷贝。所有元素在 b中的存在顺序与它们在 a中的存在顺序完全相同(假设它有一个顺序)。

同样的,呼唤

// note: instantiating with a.size() gives `b` enough capacity to hold everything
List<String> b = new ArrayList<String>(a.size());
Collections.copy(b, a);

也会在 b中创建 a的浅拷贝。如果第一个参数 b没有足够的 容量(不是大小)来包含所有 a的元素,那么它将抛出一个 IndexOutOfBoundsException。预计 Collections.copy不需要任何分配就可以工作,如果有的话,它就会抛出异常。要求预分配复制的集合(b)是一种优化,但我通常认为这个特性不值得,因为基于构造函数的替代方案(如上图所示,没有奇怪的副作用)需要进行必要的检查。

要创建深度副本,List必须通过任何一种机制对底层类型有复杂的了解。在 String的情况下,它在 Java 中是不可变的(和。NET) ,你甚至不需要一个深度拷贝。对于 MySpecialObject,您需要知道如何对其进行深度拷贝,而这不是一般操作。


注意: 最初接受的答案是在谷歌的 Collections.copy的最高结果,它是完全错误的,在评论中指出。

斯蒂芬•卡图尔卡(Stephen Katulka)的答案(公认的答案)是错误的(第二部分)。 它解释了 Collections.copy(b, a);做了一个深度拷贝,但是它没有。new ArrayList(a);Collections.copy(b, a);都只做一个浅拷贝。区别在于,构造函数分配新的内存,而 copy(...)不分配,这使得它适合于可以重用数组的情况,因为它在这方面具有性能优势。

Java 标准 API 试图阻止使用深度副本,因为如果新的编码人员经常使用这种方法,那将是很糟糕的,这也可能是 clone()在默认情况下不是公共的原因之一。

Collections.copy(...)的源代码见第552行: Http://www.java2s.com/open-source/java-document/6.0-jdk-core/collections-jar-zip-logging-regex/java/util/collections.java.htm

如果需要深度副本,则必须手动迭代这些项,使用 for 循环并在每个对象上克隆()。

可以对字符串进行深度复制

List<String> b = new ArrayList<String>(a);

因为它们是不可变的,所有其他对象都不是—— > 你需要自己迭代并做一个拷贝。

正如海瑞所说。Stephen Katulka 选择的答案包含一条关于 Collections.copy 的注释,该注释是不正确的。作者可能接受了它,因为第一行代码正在做他想要的副本。对 Collections.copy 的额外调用只是再次复制。(导致复制发生两次)。

下面的代码可以证明这一点。

public static void main(String[] args) {


List<String> a = new ArrayList<String>();
a.add("a");
a.add("b");
a.add("c");
List<String> b = new ArrayList<String>(a);


System.out.println("There should be no output after this line.");


// Note, b is already a shallow copy of a;
for (int i = 0; i < a.size(); i++) {
if (a.get(i) != b.get(i)) {
System.out.println("Oops, this was a deep copy."); // Note this is never called.
}
}


// Now use Collections.copy and note that b is still just a shallow copy of a
Collections.copy(b, a);
for (int i = 0; i < a.size(); i++) {
if (a.get(i) != b.get(i)) {
System.out.println("Oops, i was wrong this was a deep copy"); // Note this is never called.
}
}


// Now do a deep copy - requires you to explicitly copy each element
for (int i = 0; i < a.size(); i++) {
b.set(i, new String(a.get(i)));
}


// Now see that the elements are different in each
for (int i = 0; i < a.size(); i++) {
if (a.get(i) == b.get(i)) {
System.out.println("oops, i was wrong, a shallow copy was done."); // note this is never called.
}
}
}

这里的大多数答案都没有意识到这个问题,用户希望有一个从第一个列表到第二个列表的元素的 COPY,目标列表元素是新的对象,而不是引用原始列表的元素。 (意味着更改第二个列表的元素不应更改源列表相应元素的值。) 对于可变对象,我们不能使用 ArrayList (Collection)构造函数,因为它将简单地引用原始 list 元素,并且不会复制。 复制时,需要为每个对象提供一个列表克隆程序。

每一个其他的 Object 不—— > 你需要自己迭代和做一个拷贝。

为了避免这个实现可克隆。

public class User implements Serializable, Cloneable {


private static final long serialVersionUID = 1L;


private String user;
private String password;
...


@Override
public Object clone() {
Object o = null;
try {
o = super.clone();
} catch(CloneNotSupportedException e) {
}
return o;
}
}

....

  public static void main(String[] args) {


List<User> userList1 = new ArrayList<User>();


User user1 = new User();
user1.setUser("User1");
user1.setPassword("pass1");
...


User user2 = new User();
user2.setUser("User2");
user2.setPassword("pass2");
...


userList1 .add(user1);
userList1 .add(user2);


List<User> userList2 = new ArrayList<User>();




for(User u: userList1){
u.add((User)u.clone());
}


//With this you can avoid
/*
for(User u: userList1){
User tmp = new User();
tmp.setUser(u.getUser);
tmp.setPassword(u.getPassword);
...
u.add(tmp);
}
*/


}

你为什么不用 addAll方法:

    List a = new ArrayList();
a.add("1");
a.add("abc");


List b = b.addAll(listA);


//b will be 1, abc

即使 b 中已经存在项,或者想要在它后面挂起一些元素,例如:

List a = new ArrayList();
a.add("1");
a.add("abc");


List b = new ArrayList();
b.add("x");
b.addAll(listA);
b.add("Y");


//b will be x, 1, abc, Y

如果你正在使用谷歌番石榴,一行的解决方案将是

List<String> b = Lists.newArrayList(a);

这将创建一个可变的数组列表实例。

下面的输出说明了使用复制建构子和 Collections.copy ()的结果:

Copy [1, 2, 3] to [1, 2, 3] using copy constructor.


Copy [1, 2, 3] to (smaller) [4, 5]
java.lang.IndexOutOfBoundsException: Source does not fit in dest
at java.util.Collections.copy(Collections.java:556)
at com.farenda.java.CollectionsCopy.copySourceToSmallerDest(CollectionsCopy.java:36)
at com.farenda.java.CollectionsCopy.main(CollectionsCopy.java:14)


Copy [1, 2] to (same size) [3, 4]
source: [1, 2]
destination: [1, 2]


Copy [1, 2] to (bigger) [3, 4, 5]
source: [1, 2]
destination: [1, 2, 5]


Copy [1, 2] to (unmodifiable) [4, 5]
java.lang.UnsupportedOperationException
at java.util.Collections$UnmodifiableList.set(Collections.java:1311)
at java.util.Collections.copy(Collections.java:561)
at com.farenda.java.CollectionsCopy.copyToUnmodifiableDest(CollectionsCopy.java:68)
at com.farenda.java.CollectionsCopy.main(CollectionsCopy.java:20)

完整程序的源代码在这里: Java 列表副本

为了理解为什么 Collections.copy ()会抛出 IndexOutOfbound sException,尽管你已经让目标列表的后台数组足够大(通过 source 列表上的 size ()调用) ,请参阅 Abhay Yadav 在这个相关问题中的答案: 如何将 java.util.List 复制到另一个 java.util.List 中

private List<Item> cloneItemList(final List<Item> items)
{
Item[] itemArray = new Item[items.size()];
itemArray = items.toArray(itemArray);
return Arrays.asList(itemArray);
}

Java8是空安全的,您可以使用以下代码。

List<String> b = Optional.ofNullable(a)
.map(list -> (List<String>) new ArrayList<>(list))
.orElseGet(Collections::emptyList);

或者用收集器

List<String> b = Optional.ofNullable(a)
.map(List::stream)
.orElseGet(Stream::empty)
.collect(Collectors.toList())