为什么一个 ArrayList 上的 clear() 会清除另一个 ArrayList 中的元素?
Why clear() on one ArrayList is clearing elements in another ArrayList?
我想要的输出应该是:
[[1], [1, 2], [1, 2, 3], [1, 2, 3, 4], [1, 2, 3, 4, 5]]
通过每次创建新的 ArrayList 对象
List<List<Integer>> result = new ArrayList<>();
for(int i = 1; i <= 5; ++i){
List<Integer> list = new ArrayList<>();
for(int j = 1; j <= i; ++j){
list.add(j);
}
result.add(list);
}
System.out.println(result);
我得到了想要的输出
[[1], [1, 2], [1, 2, 3], [1, 2, 3, 4], [1, 2, 3, 4, 5]]
但是,当尝试使用 clear()
时没有得到相同的结果(以避免在每次迭代中创建新对象)并且 result Arraylist
在调用 clear()
时也会变空list Arraylist
List<Integer> list = new ArrayList<>();
List<List<Integer>> result = new ArrayList<>();
for(int i = 1; i <= 5; ++i){
for(int j = 1; j <= i; ++j){
list.add(j);
}
result.add(list);
list.clear();
}
System.out.println(result);
我使用 clear() 得到以下输出
[[], [], [], [], []]
--> 不是我想要的输出
如何在不每次都创建新对象(ArrayList 对象)并使用 clear() 或任何其他概念的情况下实现所需的输出。
是否result and list ArrayList
指向相同的引用或有一些其他原因导致输出不正确。请让我知道关于 ArrayList 主题我不知道或缺少的东西。
tl;博士
你说:
Are result and list ArrayList pointing to the same reference
是的。
要添加副本,请更改:
result.add( list );
… 对此:
result.add( new ArrayList<>( list ) );
详情
您可能认为 result.add(list);
正在添加 list
的 内容 。但是不,该调用传递了您命名为 list
的 ArrayList
对象的 reference(指针,内存地址)。 list
的内容与该调用无关。无论是空的还是满的,列表本身,即容器,就是您要传递给 add
.
的内容
所以您将同一个列表添加了五次。 result
的所有元素都指向同一个列表。如果添加到包含的列表,result
的所有五个元素都会看到该更改。如果清除添加的 list
,result
的所有五个元素都会看到该更改,result
的所有五个元素都指向同一个现在为空的列表。
要添加不同的 unmodifiable lists,调用 List.copyOf
。
result.add( List.copyOf( list ) );
要添加不同的可修改列表,请构造新的 ArrayList
对象。将现有列表传递给新列表的构造函数。
result.add( new ArrayList<>( list ) );
您更改为使用相同的列表,恰恰会产生您所看到的后果。您多次将同一个列表添加到 result
列表,最后一次 clear()
调用导致结果列表的所有元素显示为空。
您可以通过将反映当前内容的列表副本添加到结果来解决此问题,但结果比您已有的原始解决方案效率低,在每个循环的主体中创建一个新列表迭代,无需额外的复制操作。
当您想要包含不同内容的列表时,没有办法绕过创建不同的列表对象。但是您可以避免在这种特定情况下使用不同的存储空间:
List<List<Integer>> result = new ArrayList<>();
List<Integer> list = new ArrayList<>();
for(int i = 1; i <= 5; i++) list.add(i);
for(int j = 1; j <= 5; j++) result.add(list.subList(0, j));
System.out.println(result);
这将首先创建最大的列表,它将实际存储 Integer
个对象。第二个循环中使用的 subList
操作在这个列表中创建了一个 view,没有自己的存储空间,只是不同的边界。
这也意味着当您之后修改原始 list
时,这可能会改变结果(当您 set
元素时)或使子列表无效(当您添加或删除元素时).
您可以通过使用不可变列表来避免这种情况
List<List<Integer>> result = new ArrayList<>();
List<Integer> list = new ArrayList<>();
for(int i = 1; i <= 5; i++) list.add(i);
list = List.copyOf(list);
for(int j = 1; j <= 5; j++) result.add(list.subList(0, j));
System.out.println(result);
这只对不可变列表进行一次复制操作(需要 Java 10),而不是在每次循环迭代中进行。 subList
返回的列表也是不可变的。
从JDK16开始,创建不可变列表时甚至可以避免临时ArrayList
和复制操作
List<List<Integer>> result = new ArrayList<>();
List<Integer> list = IntStream.rangeClosed(1, 5).boxed().toList();
for(int j = 1; j <= 5; j++) result.add(list.subList(0, j));
System.out.println(result);
虽然 Stream API 可能有初始开销,但不会通过保存仅五个元素的复制操作来补偿。
适用于所有变体的一个小改进是不对最后一个元素使用 subList
,它与整个列表具有相同的内容,即代替
for(int j = 1; j <= 5; j++) result.add(list.subList(0, j));
你可以使用
for(int j = 1; j < 5; j++) result.add(list.subList(0, j));
result.add(list);
我想要的输出应该是:
[[1], [1, 2], [1, 2, 3], [1, 2, 3, 4], [1, 2, 3, 4, 5]]
通过每次创建新的 ArrayList 对象
List<List<Integer>> result = new ArrayList<>();
for(int i = 1; i <= 5; ++i){
List<Integer> list = new ArrayList<>();
for(int j = 1; j <= i; ++j){
list.add(j);
}
result.add(list);
}
System.out.println(result);
我得到了想要的输出
[[1], [1, 2], [1, 2, 3], [1, 2, 3, 4], [1, 2, 3, 4, 5]]
但是,当尝试使用 clear()
时没有得到相同的结果(以避免在每次迭代中创建新对象)并且 result Arraylist
在调用 clear()
时也会变空list Arraylist
List<Integer> list = new ArrayList<>();
List<List<Integer>> result = new ArrayList<>();
for(int i = 1; i <= 5; ++i){
for(int j = 1; j <= i; ++j){
list.add(j);
}
result.add(list);
list.clear();
}
System.out.println(result);
我使用 clear() 得到以下输出
[[], [], [], [], []]
--> 不是我想要的输出
如何在不每次都创建新对象(ArrayList 对象)并使用 clear() 或任何其他概念的情况下实现所需的输出。
是否result and list ArrayList
指向相同的引用或有一些其他原因导致输出不正确。请让我知道关于 ArrayList 主题我不知道或缺少的东西。
tl;博士
你说:
Are result and list ArrayList pointing to the same reference
是的。
要添加副本,请更改:
result.add( list );
… 对此:
result.add( new ArrayList<>( list ) );
详情
您可能认为 result.add(list);
正在添加 list
的 内容 。但是不,该调用传递了您命名为 list
的 ArrayList
对象的 reference(指针,内存地址)。 list
的内容与该调用无关。无论是空的还是满的,列表本身,即容器,就是您要传递给 add
.
所以您将同一个列表添加了五次。 result
的所有元素都指向同一个列表。如果添加到包含的列表,result
的所有五个元素都会看到该更改。如果清除添加的 list
,result
的所有五个元素都会看到该更改,result
的所有五个元素都指向同一个现在为空的列表。
要添加不同的 unmodifiable lists,调用 List.copyOf
。
result.add( List.copyOf( list ) );
要添加不同的可修改列表,请构造新的 ArrayList
对象。将现有列表传递给新列表的构造函数。
result.add( new ArrayList<>( list ) );
您更改为使用相同的列表,恰恰会产生您所看到的后果。您多次将同一个列表添加到 result
列表,最后一次 clear()
调用导致结果列表的所有元素显示为空。
您可以通过将反映当前内容的列表副本添加到结果来解决此问题,但结果比您已有的原始解决方案效率低,在每个循环的主体中创建一个新列表迭代,无需额外的复制操作。
当您想要包含不同内容的列表时,没有办法绕过创建不同的列表对象。但是您可以避免在这种特定情况下使用不同的存储空间:
List<List<Integer>> result = new ArrayList<>();
List<Integer> list = new ArrayList<>();
for(int i = 1; i <= 5; i++) list.add(i);
for(int j = 1; j <= 5; j++) result.add(list.subList(0, j));
System.out.println(result);
这将首先创建最大的列表,它将实际存储 Integer
个对象。第二个循环中使用的 subList
操作在这个列表中创建了一个 view,没有自己的存储空间,只是不同的边界。
这也意味着当您之后修改原始 list
时,这可能会改变结果(当您 set
元素时)或使子列表无效(当您添加或删除元素时).
您可以通过使用不可变列表来避免这种情况
List<List<Integer>> result = new ArrayList<>();
List<Integer> list = new ArrayList<>();
for(int i = 1; i <= 5; i++) list.add(i);
list = List.copyOf(list);
for(int j = 1; j <= 5; j++) result.add(list.subList(0, j));
System.out.println(result);
这只对不可变列表进行一次复制操作(需要 Java 10),而不是在每次循环迭代中进行。 subList
返回的列表也是不可变的。
从JDK16开始,创建不可变列表时甚至可以避免临时ArrayList
和复制操作
List<List<Integer>> result = new ArrayList<>();
List<Integer> list = IntStream.rangeClosed(1, 5).boxed().toList();
for(int j = 1; j <= 5; j++) result.add(list.subList(0, j));
System.out.println(result);
虽然 Stream API 可能有初始开销,但不会通过保存仅五个元素的复制操作来补偿。
适用于所有变体的一个小改进是不对最后一个元素使用 subList
,它与整个列表具有相同的内容,即代替
for(int j = 1; j <= 5; j++) result.add(list.subList(0, j));
你可以使用
for(int j = 1; j < 5; j++) result.add(list.subList(0, j));
result.add(list);