如何复制Java收藏夹列表


141

我有一个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()脑海中就变成了完全没用的功能。因此,除了编写一个复制函数(我现在要做)之外,是否有适当的方法来做到这一点?


Collections.copy()的文档说:“目标列表必须至少与源列表一样长。”
DJClayworth

21
我认为接受的答案不正确
Bozho 2011年

3
您接受的答案不正确,碧玉地板。衷心希望您不要在代码中使用错误的信息!
马尔科姆

Answers:


115

呼唤

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

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

还创建的浅表副本ab。如果第一个参数的容量(大小)b不足以容纳所有的元素,则它将抛出。期望工作将不需要任何分配,如果有分配,那么它将抛出该异常。这是一种优化的方法,要求将复制的集合预先分配(),但是我通常认为该功能不值得,因为给定了基于构造函数的替代方法(如上所示)没有奇怪的副作用,因此需要进行检查。aIndexOutOfBoundsExceptionCollections.copyb

若要创建深层副本,则必须List通过任一机制对基础类型都有复杂的知识。对于Strings(在Java中是不变的)(甚至是.NET),您甚至不需要深层副本。对于MySpecialObject,您需要知道如何对其进行深拷贝,而这不是一般操作。


注意:最初接受的答案是Collections.copy在Google中获得最高结果的答案,并且完全没有错,正如评论中指出的那样。


1
@ncasas是的。我很遗憾Java中没有通用的“复制”功能。在实践中,我经常发现其他所有作者都没有为他们的课程实现clone()。它使一个对象无法进行任何形式的对象复制。或者,更糟糕的是,我看到了一个没有任何文献或文献不多的实现的克隆方法,这使克隆功能无法使用(在可靠和实用的“知道发生了什么”的意义上)。
马尔科姆

133

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

正如MrWiggles指出的那样,在提供的示例中,使用采用集合的ArrayList构造函数是这种方式。

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


58

做就是了:

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

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


7
如下面的评论所述,这是一个浅表副本。否则,这将是一个很好的答案。我想我应该指定那个。没关系,我还是继续前进。
碧玉楼

11
对于字符串列表,深复制并不重要,因为String对象是不可变的。
德里克·马哈尔

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

如果需要深层副本,则必须在每个对象上使用for循环和clone()来手动遍历项目。


12

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

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'

为什么说“浅”副本?-我是Java
新手

4
“浅拷贝”是指拷贝后b中的对象与a中的对象相同,而不是它们的副本。
DJClayworth

1
Collections.copy()的javadoc说:“目标列表必须至少与源列表一样长。”
DJClayworth

我想我的意思是说,我花了一些时间才能看清该函数的实际功能,并且可以看到提问者对其确切的功能感到有些困惑
Gareth Davis,2009年

我不确定这很重要吗?由于String是不可变的,因此仅引用是不同的。但是,即使您尝试突变任一列表中的元素,也绝不会突变另一列表中的同一元素
David T.

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

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

List b = new ArrayList(a);

8

正如houiui所提到的。从斯蒂芬·卡图尔卡(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.
        }
    }
}

5

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


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

3

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

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

List a = new ArrayList(b);

3

可以使用以下方式深度复制字符串

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

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


8
这仍然是一个浅表副本,因为array中的每个元素都b指向中的相同对应String对象a。但是,这并不重要,因为正如您所指出的,String对象是不可变的。
德里克·马哈尔

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

4
请为您的答案添加一些解释
Sampada '16

1
尽管此代码可以回答问题,但提供有关如何和/或为什么解决问题的其他上下文将提高答案的长期价值。
Michael Parker

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

  }

2
不应该是“ userList2.add((User)u.clone());” ?
KrishPrabakar 2012年

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()的行为。


1

如果您使用的是Google番石榴,那么一线解决方案是

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

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


1

如果Java 8是null安全的,则可以使用以下代码。

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

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 BUG 6350752


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.