Java集合副本列表 – 我不明白
我有一个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
不够大,不能持有。 是的,我知道b
大小是0,但是现在应该足够大了,不是吗? 如果我必须先填充b
,那么Collections.copy()
在我脑海中变成一个完全无用的函数。 所以,除了编程一个复制function(我现在要做的)是否有一个适当的方法来做到这一点?
调用
List<String> b = new ArrayList<String>(a);
在b
创build一个浅的副本。 所有的元素将以b
中的顺序存在于b
中(假设它有一个顺序)。
同样,打电话
// note: instantiating with a.size() gives `b` enough capacity to hold everything List<String> b = new ArrayList<String>(a.size()); Collections.copy(b, a);
也创build一个b
内的浅拷贝。 如果第一个参数b
没有足够的容量 (不是大小)来包含所有的元素,那么它将抛出一个IndexOutOfBoundsException
。 期望是Collections.copy
不需要任何分配来工作,如果有的话,它会抛出exception。 这是一个优化,要求复制的集合被预先分配( b
),但我通常不认为该function是值得的,由于所需的检查给予基于构造函数的替代品,如上所示,没有奇怪的副作用。
要创build深层副本,通过任何一种机制, List
都必须具有复杂的底层types知识。 对于在Java中不可变的String
(和.NET),您甚至不需要深度复制。 在MySpecialObject
的情况下,你需要知道如何做一个深层次的副本,这不是一个通用的操作。
注意:最初接受的答案是Google的Collections.copy
的最高结果,正如评论中指出的那样,答案是错误的。
b
的容量为3,但大小为ArrayList
具有某种缓冲区容量的事实是一个实现细节 – 它不是List
接口的一部分,所以Collections.copy(List, List)
不使用它。 这对于特殊的ArrayList
来说会很难看。
正如MrWiggles所指出的那样,使用ArrayList构造函数接收一个集合是在示例中提供的方式。
对于更复杂的场景(可能包含真实的代码),您可能会发现Google Java Collections库很有用。
做就是了:
List a = new ArrayList(); a.add("a"); a.add("b"); a.add("c"); List b = new ArrayList(a);
ArrayList有一个构造函数,它将接受另一个Collection来复制元素
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正则expression式/ JAVA / UTIL / Collections.java.htm
如果您需要深度复制,则必须手动迭代这些项目,在每个对象上使用for循环和clone()。
复制列表最简单的方法是将其传递给新列表的构造函数:
List<String> b = new ArrayList<>(a);
b
将是一个浅的副本
看着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(a.size())
不设置大小。 它设置了初始容量(在需要resize之前可以容纳多less元素)。 在这种情况下,更简单的复制方法是:
List b = new ArrayList(a);
正如hoijui提到的。 Stephen Katulka所选的答案中包含关于Collections.copy的评论,这是不正确的。 作者可能会接受它,因为第一行代码是在做他想要的副本。 对Collections.copy的额外调用只是再次复制。 (导致复制发生两次)。
这里是代码来certificate它。
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)构造函数,因为它将简单地引用原始列表元素,并且不会复制。 复制时需要为每个对象设置一个列表复制器。
为什么不使用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
如果要复制ArrayList,请使用以下命令复制它:
List b = new ArrayList(); b.add("aa"); b.add("bb"); List a = new ArrayList(b);
string可以被深度复制
List<String> b = new ArrayList<String>(a);
因为它们是不可改变的。 其他的对象不是 – >你需要迭代并自己做一个副本。
private List<Item> cloneItemList(final List<Item> items) { Item[] itemArray = new Item[items.size()]; itemArray = items.toArray(itemArray); return Arrays.asList(itemArray); }
其他的对象不是 – >你需要迭代并自己做一个副本。
为了避免这个实现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); } */ }
以下输出说明了使用复制构造函数和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列表副本 。 但是输出足以查看java.util.Collections.copy()的行为。
如果你使用谷歌番石榴,一线解决scheme将是
List<String> b = Lists.newArrayList(a);
这将创build一个可变数组列表实例。
如果您想象用例将某些值复制到现有集合中,则复制不是无用的。 即你想覆盖现有的元素,而不是插入。
例如: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]
不过,我期望一个复制方法,将采取额外的参数为源和目标集合的开始索引,以及为count的参数。
请参阅Java BUG 6350752
为了理解为什么Collections.copy()抛出一个IndexOutOfBoundsException,虽然你已经使目标列表的支持数组足够大(通过sourceList上的size()调用),请参阅Abhay Yadav在这个相关问题中的答案: 将java.util.List复制到另一个java.util.List