Java ArrayList 副本

Posted

技术标签:

【中文标题】Java ArrayList 副本【英文标题】:Java ArrayList copy 【发布时间】:2011-09-26 00:54:47 【问题描述】:

我有一个大小为 10 的 ArrayList l1。我将 l1 分配给新的列表引用类型 l2l1l2 会指向同一个 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();

除了创建 2 个列表对象以及对集合进行从旧到新的复制之外,是否没有其他方法可以将列表对象的副本分配给新的引用变量?

【问题讨论】:

【参考方案1】:

是的,赋值只会将l1(这是一个引用)的复制到l2。它们都将引用同一个对象。

创建一个浅拷贝非常简单:

List<Integer> newList = new ArrayList<>(oldList);

(仅作为一个例子。)

【讨论】:

是否可以仅将数组列表的一部分复制到新的数组列表中,高效。例如:将位置 5 和 10 之间的元素从一个数组列表复制到另一个新数组列表。在我的应用程序中,范围会更大。 @Ashwin:这是一个 O(N) 操作,但是是的……您可以使用 List.subList 来“查看”原始列表的一部分。 如果数组列表嵌套(ArrayList&lt;ArrayList&lt;Object&gt;&gt;)怎么办?这会递归地创建所有子 ArrayList 对象的副本吗? @Cat:不...这只是一个浅拷贝。 @ShanikaEdiriweera:你可以用流利的方式做到这一点,是的。但棘手的部分是创建一个 大多数 对象不会提供的深层副本。如果您有具体案例,我建议您提出一个包含详细信息的新问题。【参考方案2】:

尝试使用Collections.copy(destination, source);

【讨论】:

请解释一下为什么这可能比new ArrayList&lt;&gt;(source);更可取? @atc 这是另一种进行浅拷贝的方法,而不是 new ArrayList() 它使用了另一种算法,可用于任何 List 实现,而不仅仅是 ArrayList,仅此而已:) 这种方法很容易误导人!实际上,它的描述是。它说:“将元素从一个源列表复制到目标”,但它们没有被复制!它们被引用,所以只有 1 个对象的副本,如果它们是可变的,你就有麻烦了 在 java-api 中没有任何地方可以通过任何集合类进行深度克隆 这个答案没有多大意义。 Collections.copy 根本不是 new ArrayList&lt;&gt;(source) 的替代品。 Collections.copy 实际上所做的是假设destination.size() 至少和source.size() 一样大,然后使用set(int,E) 方法逐个索引地复制范围。该方法不会向目标添加新元素。 Refer to the source code if it's not clear enough from the Javadoc.【参考方案3】:

是的,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&lt;String&gt; l2 = new ArrayList&lt;String&gt;(l1)List&lt;String&gt; l2 = l1之间的区别吗? @MortalMan 不同之处在于 l2 = new ArrayList(l1) 是一个全新的对象,修改 l2 不会影响 l1,而 List l2 = l1 你没有创建一个新对象,但只是引用与 l1 相同的对象,因此在这种情况下,执行诸如 l2.add("Everybody")、l1.size() 和 l2.size() 之类的操作将返回 3,因为两者都引用相同对象。【参考方案4】:

另一种将值从 src ArrayList 复制到 dest Arraylist 的便捷方法如下:

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

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

【讨论】:

我不完全确定这是准确的。我的测试显示相反(仍然引用同一个对象) 这个解决方案在使用 ArrayList 和 ArrayAdapter 时对我有用 这个答案是错误的。 addAll() 只是像 invertigo 所说的那样复制引用。这不是深拷贝。 对于 ArrayList,这个答案是可以接受的,因为 String 是不可变的,但是用 OP 的例子 ArraList 试试,你会发现它只是在复制引用。跨度> 我猜这不是我的日子。事实证明,Integer 和 Long 等类也是不可变的,因此 Harshal 的答案适用于 ArrayList 和 ArrayList 等简单情况。它失败的地方是不可变的复杂对象。【参考方案5】:

有一个方法 addAll() 用于将一个 ArrayList 复制到另一个。

例如你有两个数组列表:sourceListtargetList,使用下面的代码。

targetList.addAll(sourceList);

【讨论】:

它也只是复制引用。【参考方案6】:

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

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

【讨论】:

如何制作“显式副本”?我猜你是在说深拷贝?【参考方案7】:

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`.

“不可修改”是指列表中元素的数量,以及作为元素保存在每个槽中的对象引用是固定的。您不能添加、删除或替换元素。但是每个元素中保存的对象引用可能是也可能不是mutable。

colors.remove( 2 ) ;          // SUCCEEDS. 
masterColors.remove( 2 ) ;    // FAIL - ERROR.

看到这个code run live at 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 对象来表示颜色,颜色对象在内存中的某个地方浮动。这两个列表持有指向相同颜色对象的指针。这是一张图表。

第一个列表colors 是可修改的。这意味着可以删除一些元素,如上面的代码所示,我们删除了原始的第三个元素Chartreuse(索引 2 = 序数 3)。并且可以添加元素。并且可以将元素更改为指向其他一些String,例如OliveDrabCornflowerBlue

相比之下,masterColors 的四个元素是固定的。没有去除,没有添加,也没有替代另一种颜色。 List 的实现是不可修改的。

【讨论】:

【参考方案8】:

只是为了完成: 上面的所有答案都是为了浅拷贝 - 保留原始对象的引用。我你想要一个深拷贝,你在列表中的(参考)类必须实现一个 clone/copy 方法,它提供单个对象的深拷贝。然后你可以使用:

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

【讨论】:

以上是关于Java ArrayList 副本的主要内容,如果未能解决你的问题,请参考以下文章

Java ArrayList 副本

如何制作Java ArrayList的深层副本[重复]

java中ArrayList和LinkedList区别

Java7集合框架——ArrayList

简易版Java ArrayList(增删改查)

关于Arrays.asList()返回的ArrayList