148

我有一个ArrayList,我想准确地复制它。我尽可能使用实用程序类,假设有人花了一些时间使它正确。所以很自然地,我最终得到了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()在我的脑海中就变成了一个完全没用的功能。那么,除了编写一个复制功能(我现在要做的)之外,有没有合适的方法来做到这一点?

4

18 回答 18

133

b容量为 3,但大小为 0。具有某种缓冲区容量的事实是实现细节 - 它不是接口的一部分因此不使用它。特殊情况会很难看。ArrayListListCollections.copy(List, List)ArrayList

正如 tddmonkey 所指出的,在提供的示例中,使用带有集合的 ArrayList 构造函数是方法。

对于更复杂的场景(可能包括您的真实代码),您可能会发现Guava中的集合很有用。

于 2009-03-27T11:25:22.113 回答
122

打电话

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

a创建within的浅表副本b。所有元素都将以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);

a还创建了within的浅表副本b。如果第一个参数 ,b没有足够的容量(不是大小)来包含 的所有a元素,那么它将抛出一个IndexOutOfBoundsException. 期望是不需要分配Collections.copy来工作,如果有,那么它会抛出该异常。要求预先分配复制的集合是一种优化b

要创建深层副本,List无论通过哪种机制,都必须对底层类型有复杂的了解。在Strings 的情况下,它在 Java(和 .NET 中)是不可变的,您甚至不需要深拷贝。在 的情况下MySpecialObject,您需要知道如何对其进行深层复制,这不是通用操作。


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

于 2009-11-06T03:06:37.020 回答
60

做就是了:

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

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

于 2009-03-27T11:25:11.723 回答
17

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/集合.java.htm

如果你需要一个深拷贝,你必须手动迭代这些项目,在每个对象上使用 for 循环和 clone()。

于 2011-02-24T16:38:33.410 回答
12

复制 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'
于 2009-03-27T11:25:42.223 回答
9
List b = new ArrayList(a.size())

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

List b = new ArrayList(a);
于 2009-03-27T11:26:40.147 回答
8

正如 hoijui 提到的。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.
        }
    }
}
于 2011-04-07T16:37:41.297 回答
6

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

于 2011-10-10T09:29:01.150 回答
5

你为什么不只使用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
于 2013-05-01T01:04:31.187 回答
3

如果要复制 ArrayList,请使用以下命令进行复制:

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

List a = new ArrayList(b);
于 2009-03-27T11:25:59.913 回答
3

字符串可以被深度复制

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

因为它们是不可变的。不是其他所有对象->您需要自己迭代并进行复制。

于 2011-04-05T16:42:59.347 回答
3
private List<Item> cloneItemList(final List<Item> items)
    {
        Item[] itemArray = new Item[items.size()];
        itemArray = items.toArray(itemArray);
        return Arrays.asList(itemArray);
    }
于 2016-07-27T14:48:41.903 回答
1

不是其他所有对象->您需要自己迭代并进行复制。

为了避免这种情况,请实现 Cloneable。

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);               
        }
       */

  }
于 2011-11-28T05:57:03.110 回答
1

如果您使用的是 google guava,那么单线解决方案将是

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

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

于 2013-10-11T14:56:25.333 回答
1

以下输出说明了使用复制构造函数和 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 List copy。但输出足以了解 java.util.Collections.copy() 的行为方式。

于 2015-07-30T17:40:33.483 回答
1

由于 Java 8 是空安全的,您可以使用以下代码。

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())
于 2017-12-14T12:24:42.637 回答
0

如果您想象将某些值复制到现有集合中的用例,则复制并非无用。即你想覆盖现有元素而不是插入。

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

但是,我希望有一个复制方法可以为源和目标集合的起始索引以及计数参数提供额外的参数。

请参阅 Java 错误6350752

于 2009-03-27T17:16:14.157 回答
-1

要了解为什么 Collections.copy() 会引发 IndexOutOfBoundsException,尽管您已经使目标列表的后备数组足够大(通过对 sourceList 的 size() 调用),请参阅 Abhay Yadav 在此相关问题中的回答: 如何将 java.util.List 复制到另一个 java.util.List

于 2016-04-27T18:48:25.517 回答