如何在Java中克隆一个通用列表?

179

我有一个 ArrayList<String>,希望返回它的一个副本。 ArrayList 有一个克隆方法,其签名如下:

public Object clone()

调用这个方法之后,我该如何将返回的对象转换回ArrayList<String>

17
不,这是一个有效的问题。由于Java在运行时擦除类型信息,因此不支持“真正”的泛型,因此这些细节可能会很棘手。此外,Cloneable接口和Object.clone()方法的机制同样令人困惑。 - Tim Frey
1
好的,我主要使用C#,在这方面非常容易。如果你想让我从这个问题中删除注释,请告诉我。 - Espo
1
你可以不用留言了。我认为我的编辑已经解释清楚了我遇到的问题。 - Bill the Lizard
2
你的评论还可以,只是有点傲慢。我想很多Java开发人员必须跨越的障碍在.NET开发人员看来似乎有些愚蠢。 - Tim Frey
1
@奥斯卡,他想要克隆,而不是调用克隆命令。如果复制品并没有真正克隆,那么它可能不同。我认为这就是问题所在。这确实是一个棘手的问题。 - Rafa
显示剩余2条评论
14个回答

351

为什么要克隆?一般来说,创建一个新列表更有意义。

List<String> strs;
...
List<String> newStrs = new ArrayList<>(strs);

完成工作。


17
您可能不知道这是什么类型的列表。它可能是一个LinkedList、MyOwnCustomList或ArrayList的子类,如果是后者,则新建一个ArrayList会是错误的类型。 - Steve Kuo
58
我会关心原始列表使用了哪种实现方式吗?我可能会关心新列表使用哪种实现方式。 - Tom Hawtin - tackline
14
@Steve Kuo:该签名是ArrayList(Collection<? extends E> c),意味着你可以使用任何类型的列表作为参数,这并不重要。 - cdmckay
3
我的意思是它不是深拷贝,是吗? - user1545072
4
不,它不会是这样。 - Tom Hawtin - tackline
显示剩余9条评论

65
ArrayList newArrayList = (ArrayList) oldArrayList.clone();

45
对于字符串而言,这个方法可以正常工作(这也是问题所要求的),但值得注意的是,ArrayList.clone 方法将执行浅拷贝。因此,如果列表中含有可变对象,则它们不会被克隆(在一个列表中更改一个对象也会在另一个列表中更改该对象)。 - pkaeding
51
除了遗留代码外,应避免使用原始类型。最好使用ArrayList<String> newArrayList = (ArrayList<String>) oldArrayList.clone(); - cdmckay
21
太遗憾了,ArrayList拥有#clone方法,但List本身却没有。唉。 - rogerdpack
12
不相关,但在此期间:左侧使用List<String>而不是ArrayList<String>。这是大多数情况下应该使用集合的方式。 - Christophe Roussy
3
我无法使用这种方法复制ArrayList。clone()方法未被识别。 - Jack
1
@Jack,你的错误在于尝试通过List接口来调用clone()方法,而不是通过ArrayList类。 - JRr

19

使用Java 8,可以使用流进行克隆。

import static java.util.stream.Collectors.toList;

...

List<AnObject> clone = myList.stream().collect(toList());

可以像这样完成 List<AnObject> xy = new ArrayList<>(oldList); - mirzak
2
这不是深拷贝,一个列表元素的更改可以在另一个列表中看到。 - Inchara
3
问题中哪里指定了需要进行深拷贝?假设需要另一个包含相同对象的集合。如果您想采用深拷贝方法,那么您就会引发一系列新问题。 - Simon Jenkins
这不完全是一个克隆,对吧?根据 API 文档:"无法保证返回的 List 的类型、可变性、可序列化性或线程安全性。" 呃,我们不想要那种东西。使用 toCollection 可能是更好的选择。 - Tom Hawtin - tackline
为什么这个答案有这么多赞?!问题明确要求克隆,即深拷贝,请给这个答案投反对票! - jan

19

这是我用来做那件事的代码:

ArrayList copy = new ArrayList (original.size());
Collections.copy(copy, original);

希望这对你有用


3
请避免使用原始类型,所以请使用ArrayList<YourObject>代替ArrayList - milosmns
2
由于列表大小不同,所以无法工作。 - MLProgrammer-CiM
为什么不直接使用ArrayList构造函数的复制重载呢? - Tom Hawtin - tackline

17

请注意,Object.clone()存在一些重大问题,在大多数情况下不建议使用。请查看Effective Java中的第11项建议,获得完整答案。我认为您可以安全地在原始类型数组上使用Object.clone(),但除此之外,您需要审慎地正确使用和覆盖clone方法。您最好定义一个复制构造函数或静态工厂方法,根据您的语义明确克隆对象。


15

我认为使用集合API可以解决问题:

注意:复制方法的运行时间为线性时间。

//assume oldList exists and has data in it.
List<String> newList = new ArrayList<String>();
Collections.copy(newList, oldList);

13
为什么不直接使用 new ArrayList<String>(oldList) 呢? - cdmckay
4
我认为这样做行不通,因为文档中提到:“目标列表的长度必须至少与源列表一样长。如果目标列表比源列表更长,则目标列表中剩余的元素将不受影响。” - Greg Domjan
@GregDomjan 我并不认为这是最好的方法,但这是一种可行的方法。要解决你的问题,只需要这样做:List<String> newList = new ArrayList<>(oldList.size()); - nckbrz
1
不确定是否与Java 8有关,但即使指定了大小,仍会在“List<MySerializableObject> copyList = new ArrayList<>(mMySerializableObjects.size());”上出现异常IndexOutOfBoundsException: destination.size() < source.size(): 0 < 2。似乎使用copyList.addAll(original);是一个很好的替代方案。 - Gene Bo
@GeneBo 它并没有指定大小,而是指定了容量,这是完全不同的概念。神秘的 int 参数带来的乐趣。我不知道为什么你想使用这个晦涩的静态方法,而不是老实用好用的 addAll - Tom Hawtin - tackline

12

我发现使用addAll很好用。

ArrayList<String> copy = new ArrayList<String>();
copy.addAll(original);

在这里,括号被用来代替泛型语法。


7
对于字符串来说没问题,但对于可变对象不行。你也需要克隆它们。 - jodonnell
是的,他的问题是关于字符串的。而且他遇到了泛型的问题,在这种情况下不太喜欢强制转换的东西。 - Allain Lalonde
另外,ArrayList.clone 只会进行浅拷贝,因此列表中的可变对象也不会使用该方法进行克隆。 - pkaeding
那应该是 ArrayList<String>。另外,最好使用 new ArrayList<String>(original),因为这样写起来更简单,而且同样清晰易懂。 - cdmckay
4
为什么不直接使用 new ArrayList<String>(original)呢? - user102008
查看 OpenJDK 8 源代码,使用 List<String> copy = new ArrayList<>(original); 具有减少开销的额外好处,与 List<String> list = new ArrayList<>(original); list.addAll(orig); 相比可能稍微快一点(尚未测试),尽管为了性能而仅使用构造函数参数而不是 addAll(Collection) 进入了过早优化的领域,但我只是使用它因为它更短而已。 - Jonathan Precise

8
List<String> shallowClonedList = new ArrayList<>(listOfStrings);

请记住,这只是浅拷贝而不是深拷贝,即您获得了一个新列表,但条目相同。对于简单的字符串来说,这没有问题。当列表条目本身是对象时,情况会变得更加棘手。


7

如果您希望为了能够在getter中返回列表,最好这样做:

ImmutableList.copyOf(list);

4
要克隆一个通用接口,比如java.util.List,只需要进行强制类型转换。以下是一个例子:
List list = new ArrayList();
List list2 = ((List) ( (ArrayList) list).clone());

虽然有些棘手,但如果你只能返回一个List接口,那么它是可行的,这样任何人在你之后都可以随时实现你的列表。

我知道这个答案已经接近最终答案了,但我的答案解释了如何在使用泛型父类List而不是ArrayList的情况下完成所有操作。


2
你假设它总是一个ArrayList,这并不正确。 - jucardi
@JuanCarlosDiaz,这是被问到的问题,它涉及ArrayList,所以我回答了ArrayList :) - Ahmed Hamdy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接