提问人:k314159 提问时间:11/13/2023 最后编辑:Basil Bourquek314159 更新时间:11/15/2023 访问量:170
为了安全起见,是否有必要复制列表?
Is it necessary to copy a list to be safe?
问:
Stream.toList 的实现(和文档)是这样的:
Collections.unmodifiableList(new ArrayList<>(Arrays.asList(this.toArray())))
我想知道为什么需要将返回的列表复制到新的.仅仅返回以下内容还不够吗?Arrays.asList
ArrayList
Collections.unmodifiableList(Arrays.asList(this.toArray()))
我想知道,如果我编写一个方法返回它像这样创建的列表,如果我不费心制作它的防御性副本,会有什么问题吗?
答:
我没有答案,但我觉得它的实现很有趣,因为它还在里面创建了一个列表:Arrays.asList()
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
我想知道发生了什么,也许是其中之一:“这仅适用于某些虚拟机,因此无法保证”
如果你不是在写一个库,老实说,我不介意太多。 顺便说一句,为了真正的安全,还需要是不可变的。T
编辑:有趣的东西,显然这是一个不同的,没什么奇怪的ArrayList
ArrayList
评论
ArrayList
Arrays
java.util.ArrayList
Arrays
ArrayList
j.u.ArrayList
Arrays.ArrayList
由可修改集合支持的不可修改视图
来自 Javadoc(强调我的):
不可修改的视图集合是不可修改的集合,也是后备集合的视图。如上所述,其 mutator 方法引发 UnsupportedOperationException,而读取和查询方法则委托给后备集合。其效果是提供对后备集合的只读访问。这对于组件提供对内部集合的读取访问权限非常有用,同时防止他们意外修改此类集合。不可修改的视图集合的示例包括 Collections.unmodifiableCollection、Collections.unmodifiableList 和相关方法返回的集合。
请注意,对后备集合的更改可能仍是可能的,如果发生更改,则通过不可修改的视图可以看到这些更改。因此,不可修改的视图集合不一定是不可变的。但是,如果不可修改视图的后备集合实际上是不可变的,或者对后备集合的唯一引用是通过不可修改的视图,则可以认为该视图实际上是不可变的。
由于返回后备列表的 a,因此仍然可以修改后备列表,这将影响不可修改的列表。现在,该列表又由方法中提供的数组支持。所以,一切都取决于.由于它位于接口的默认方法中,因此它是否返回可修改的内容取决于流的实际实现。这就是为什么它需要被复制。Collections.unmodifiableList
unmodifiable view
Arrays.asList
this.toArray()
Stream
toArray()
评论
Arrays.asList(this.toArray())
unmodifiable
toArray
Stream
Arrays.asList
toArray()
在您提供的实现中创建新 ArrayList 的原因是为了确保返回的列表确实不可修改。Arrays.asList 方法返回由原始数组支持的固定大小的列表。
获取不可修改列表的另一种更现代的方法是使用 Stream API 中的 Collectors.toUnmodifiableList() 方法。例如:
Arrays.stream(this.toArray()).collect(Collectors.toUnmodifiableList());
此方法直接生成不可修改的列表,而无需创建中间 ArrayList。我希望这个建议能有所帮助。
评论
toUnmodifiableList()
Stream.toList()
Collectors.toUnmodifiableList()
collect(Collectors.toUnmodifiableList())
toList()
Collectors.toUnmodifiableList()
null
toList()
它有一个目的
我想知道为什么需要将 Arrays.asList 返回的列表复制到新的 ArrayList。仅仅返回以下内容还不够吗?
我们在谈论哪个?如果我们谈论的是 中的那个,你是对的:该方法的 javadoc 保证它是一个新分配的数组,因此,不需要进行防御性复制。但是,这里不涉及:我们谈论的是 .它的 javadoc 要短得多:.toArray()
j.u.Collection
toArray()
toArray()
j.u.s.Stream
.toArray()
/**
* Returns an array containing the elements of this stream.
*
* <p>This is a <a href="package-summary.html#StreamOps">terminal
* operation</a>.
*
* @return an array, whose {@linkplain Class#getComponentType runtime component
* type} is {@code Object}, containing the elements of this stream
*/
与此不同的是,这个 javadoc 对该数组的性质没有任何注释。因此,显然,javadoc 中的默认 impl 并不假定数组一定是“安全的”(“安全”是指:以后不会被修改)。j.u.Collection toArray()
Stream
我想你知道,但要非常清楚:
Arrays.asList
几乎总是一个错误。这是一个两全其美的列表:它会在你传递给它的数组周围创建一个轻量级包装器。所以,有效(因为这是可能的,而且确实是这样实现的),但不能(因为你不能改变数组的大小)。该列表不是不可变的(即,如果将其传递给您直接控制之外的代码,则需要广泛记录,或者制作防御性副本),但也不是功能齐全的列表。改用它,它使列表完全不可变 - 您可以将列表传递给您喜欢的任何人,而不必担心失去对完整性的控制。set()
foo[x] = value;
.add()
List.of
编辑:并解释为什么它的 ) 而不是 - 那些静态方法上的不可变列表 make throw NPEs when 在那里,但流可以有 ,所以你不能使用它。此编辑由 @Rob Spoor 的评论带给您:)Collections.unmodifiableList(new ArrayList<...
List.copyOf
List
null
null
不过,这并不像你想象的那么大
请注意,您找到的代码只是默认实现 - 任何特定的 stream 实现都可以自由地想出更好的方法来做到这一点。几乎每个你必然会遇到的实现都会覆盖定义。例如,如果你调用 arraylist 的 ,你最终会得到这个实现,它来自 streamops,并且与核心库中所有集合使用的代码路径相同。因此,您最终使用的实际代码是这样的:stream()
@Override
public List<P_OUT> toList() {
return SharedSecrets.getJavaUtilCollectionAccess()
.listFromTrustedArrayNullsAllowed(this.toArray());
}
这里的“可信数组”指的是传递给它的数组保证不会从你下面改变的概念。编译器无法强制执行该保证,但 arraylist 的(事实上,所有未损坏的集合,因为 javadoc 需要它! 创建新的数组,因此这样做是安全的,而且效率要高得多(避免了一堆不必要的副本)。j.u.Collection
toArray()
评论
List.of
null
Stream
null
Collections.unmodifiableList(new ArrayList<>(Arrays.asList(array)))
Stream.toList()
在谈论安全时,我们通常指的是对象是否不可变
unmodifiableList
-> 不可修改 这里只保证调用方不能改变包装列表的状态,并且源的更改仍然会反映在包装列表中。new ArrayList<>
-> 防御性复制意味着修改源不会改变复制的状态,反之亦然。- 不可修改 + 防御性复制 ->不可变,您可以假设列表始终包含相同的对象引用列表。
举例说明差异
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class UnmodifiableListCopy {
public static void main(String[] args) {
String[] source = new String[]{"dummy"};
List<String> unmodifiable = unmodifiableListWithoutCopy(source);
List<String> immutable = unmodifiableListWithCopy(source);
System.out.println("unmodifiable:" + unmodifiable);
System.out.println("immutable:" + immutable);
source[0] = "modified";
System.out.println("--- change source ---");
System.out.println("unmodifiable:" + unmodifiable);
System.out.println("immutable:" + immutable);
}
private static <T> List<T> unmodifiableListWithoutCopy(T[] arr) {
return Collections.unmodifiableList(Arrays.asList(arr));
}
private static <T> List<T> unmodifiableListWithCopy(T[] arr) {
return Collections.unmodifiableList(new ArrayList<>(Arrays.asList(arr)));
}
}
运行代码时应看到以下内容。
unmodifiable:[dummy]
immutable:[dummy]
--- change source ---
unmodifiable:[modified]
immutable:[dummy]
参考
上一个:仅打印列表中最长的非重叠范围
下一个:在 Python 中准确计算方差
评论