Java ArrayList复制

271

我有一个大小为10的 ArrayList l1,我将其赋值给新的列表引用类型 l2。那么 l1l2 会指向同一个 ArrayList 对象吗?还是 ArrayList 对象的副本被分配给了 l2

当使用 l2 引用时,如果我更新列表对象,则会反映在 l1 引用类型中。

例如:

List<Integer> l1 = new ArrayList<Integer>();
for (int i = 1; i <= 10; i++) {
    l1.add(i);
}

List l2 = l1;
l2.clear();

除了创建两个列表对象,从旧的集合复制到新的集合,没有其他方法可以将列表对象的副本分配给新的引用变量吗?

10个回答

562

是的,赋值操作只会复制l1(也就是一个引用)到l2。它们都会指向同一个对象。

不过创建浅拷贝很容易:

List<Integer> newList = new ArrayList<>(oldList);
(仅以一个例子为例。)

1
有没有可能高效地将ArrayList的一部分复制到新的ArrayList中,例如:从一个ArrayList中复制位置5到10之间的元素到另一个新的ArrayList中。在我的应用程序中,范围会更大。 - Ashwin
1
@Ashwin:虽然它是一个O(N)操作,但是你可以使用List.subList来获取原始列表的“视图”以查看其某个部分。 - Jon Skeet
如果数组列表是嵌套的 (ArrayList<ArrayList<Object>>),那么会递归地创建所有子 ArrayList 对象的副本吗? - Cat
3
@猫:不...这只是一个浅拷贝。 - Jon Skeet
1
@ShanikaEdiriweera:你可以使用流来流畅地完成它。但是棘手的部分是创建一个深拷贝,大多数对象都不会提供。如果你有具体的情况,请提出一个带有详细信息的新问题。 - Jon Skeet
显示剩余11条评论

74

16
可以解释一下为什么这种方法比使用 new ArrayList<>(source) 更好吗? - Alex
8
这是一种浅拷贝的另一种方式,与使用“new ArrayList()”不同的是,它使用了另一个算法,可以用于任何List实现,而不仅仅是ArrayList,就是这样 :) - Sergii Zagriichuk
20
这个方法非常具有误导性!实际上,它的描述就是这样。它说:“将一个源列表中的元素复制到目标列表中”,但它们并没有被复制!它们被引用,因此对象只有1份拷贝,如果它们是可变的,你会遇到麻烦。 - ACV
9
在Java API中,任何集合类都没有进行深度克隆。 - Vikash
4
这个答案意思不是很清楚。Collections.copy根本不能替代new ArrayList<>(source)。实际上,Collections.copy假定destination.size()至少和source.size()一样大,并且使用set(int,E)方法一个一个地按索引复制范围。该方法不会向目标添加新元素。如果文档不够清楚,请参考源代码。 - Radiodef
这种方法不起作用。如果你需要创建一个副本,那么请使用 new ArrayList<>(oldList)。 - 4aRk Kn1gh7

36

是的,l1l2将指向同一个引用,同一个对象。

如果你想基于另一个ArrayList创建一个新的ArrayList,你可以这样做:

List<String> l1 = new ArrayList<String>();
l1.add("Hello");
l1.add("World");
List<String> l2 = new ArrayList<String>(l1); //A new arrayList.
l2.add("Everybody");
结果将是l1仍然有2个元素,而l2有3个元素。

请问您能否解释一下 List<String> l2 = new ArrayList<String>(l1)List<String> l2 = l1 之间的区别? - MortalMan
@MortalMan,区别在于l2 = new ArrayList<String>(l1)是一个全新的对象,修改l2不会影响l1,而List<String> l2 = l1则不是创建一个新对象,而只是引用与l1相同的对象,因此在这种情况下执行操作,如l2.add("Everybody")、l1.size()和l2.size()将返回3,因为两者都引用相同的对象。 - Alfredo Osorio

19

将源ArrayList中的值复制到目标ArrayList的另一种便捷方式如下:

ArrayList<String> src = new ArrayList<String>();
src.add("test string1");
src.add("test string2");
ArrayList<String> dest= new ArrayList<String>();
dest.addAll(src);

这是实际的值复制,而不仅仅是引用复制。


12
我不完全确定这一点是否准确。我的测试结果相反(仍涉及相同的对象)。 - invertigo
当我使用ArrayList和ArrayAdapter时,这个解决方案对我很有效。 - albanx
2
这个答案是错误的。addAll()只是像invertigo所说的那样复制引用。这不是深拷贝。 - jk7
对于 ArrayList<String>,这个答案是可接受的,因为 String 是不可变的,但是尝试使用 OP 的例子,一个 ArraList<Integer>,你会发现它只是复制引用。 - jk7
今天可能不是我的日子。原来像Integer和Long这样的类也是不可变的,所以Harshal的答案适用于简单情况,例如ArrayList<Integer>和ArrayList<String>。但对于不可变的复杂对象,它就会失败。 - jk7

11

有一个叫做 addAll() 的方法,可以将一个ArrayList复制到另一个ArrayList中。

例如,如果你有两个ArrayList:sourceListtargetList,使用下面的代码:

targetList.addAll(sourceList);


它也只是复制引用。 - Vikash

4

Java不传递对象,而是传递对对象的引用(指针)。因此,l2和l1是指向同一对象的两个指针。

如果需要具有相同内容的两个不同列表,则必须进行显式复制。


3
你如何制作“显式副本”?我想你在谈论深拷贝? - Cin316

1

1: 你可以使用addAll()方法

newList.addAll(oldList);

2: 如果你创建了新的ArrayList,那么你可以复制它!

ArrayList<String> newList = new ArrayList<>(OldList);

1

仅为完整性而言: 以上所有答案都是进行浅拷贝-保留原始对象的引用。如果您想要深层复制,列表中的参考类必须实现一个克隆/复制方法,该方法提供单个对象的深层复制。然后您可以使用:

newList.addAll(oldList.stream().map(s->s.clone()).collect(Collectors.toList()));

1

List.copyOf ➙ 不可修改的列表

你问道:

没有其他方法可以分配一个列表的副本吗?

Java 9引入了List.of方法,用于使用字面量创建未知具体类的不可修改List

LocalDate today = LocalDate.now( ZoneId.of( "Africa/Tunis" ) ) ;
List< LocalDate > dates = List.of( 
    today.minusDays( 1 ) ,  // Yesterday
    today ,                 // Today
    today.plusDays( 1 )     // Tomorrow
);

除此之外,我们还得到了List.copyOf。这个方法也返回一个未知具体类别的不可修改的List
List< String > colors = new ArrayList<>( 4 ) ;          // Creates a modifiable `List`. 
colors.add ( "AliceBlue" ) ;
colors.add ( "PapayaWhip" ) ;
colors.add ( "Chartreuse" ) ;
colors.add ( "DarkSlateGray" ) ;
List< String > masterColors = List.copyOf( colors ) ;   // Creates an unmodifiable `List`.

“不可修改”是指列表中的元素数量和每个元素作为对象引用所持有的对象是固定的。您无法添加、删除或替换元素。但是,每个元素持有的对象引用可能是可变的或不可变的。
colors.remove( 2 ) ;          // SUCCEEDS. 
masterColors.remove( 2 ) ;    // FAIL - ERROR.

请查看 在IdeOne.com上运行的代码

dates.toString(): [2020-02-02, 2020-02-03, 2020-02-04]

colors.toString(): [AliceBlue, PapayaWhip, DarkSlateGray]

masterColors.toString(): [AliceBlue, PapayaWhip, Chartreuse, DarkSlateGray]

你问到了对象引用。就像其他人所说,如果你创建一个列表并将其分配给两个引用变量(指针),你仍然只有一个列表。两个指针都指向同一个列表。如果你使用其中一个指针来修改列表,那么两个指针后来都会看到更改,因为内存中只有一个列表。

所以你需要复制列表。如果你想要这个副本是不可修改的,可以使用List.copyOf方法,如此回答中所讨论的那样。通过这种方法,你最终得到了两个独立的列表,每个列表都有元素,这些元素持有对相同内容对象的引用。例如,在我们上面的示例中,使用String对象表示颜色,颜色对象在内存中漂浮。这两个列表持有指向相同颜色对象的指针。下面是一个图示。

enter image description here

第一个列表colors是可修改的。这意味着像上面的代码中所看到的那样,一些元素可以被删除,我们删除了原始的第三个元素Chartreuse(索引为2 = 序数3)。元素可以添加。并且可以将元素更改为指向其他String,例如OliveDrabCornflowerBlue
相反,masterColors的四个元素是固定的。不能删除、添加或替换其他颜色。该List实现是不可修改的。

0

尝试使用ArrayList 它可以正常工作

ArrayList<Integer> oldList = new ArrayList<>(Integer);
oldList.add(1);
oldList.add(2);

ArrayList<Integer> newList = new ArrayList<>(oldList);
// now newList contains 1, 2

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