过去,我曾说过要安全地复制集合,请执行以下操作:
public static void doThing(List<String> strs) {
List<String> newStrs = new ArrayList<>(strs);
或者
public static void doThing(NavigableSet<String> strs) {
NavigableSet<String> newStrs = new TreeSet<>(strs);
但是这些“复制”构造函数,类似的静态创建方法和流,真的安全吗?规则在哪里指定?我所说的安全是指 Java 语言提供的基本语义完整性保证,以及针对恶意调用者强制执行的集合,假设有合理的
SecurityManager
支持。并且没有缺陷。我对抛出
ConcurrentModificationException
的方法很满意, NullPointerException
, IllegalArgumentException
, ClassCastException
等,或者甚至挂起。我选择了
String
作为不可变类型参数的示例。对于这个问题,我对具有自己陷阱的可变类型集合的深拷贝不感兴趣。(需要说明的是,我查看了 OpenJDK 源代码并对
ArrayList
和 TreeSet
有某种答案。)
最佳答案
对于在同一个 JVM 中运行的普通 API(如 Collection API)中的故意恶意代码,没有真正的保护措施。
很容易证明:
public static void main(String[] args) throws InterruptedException {
Object[] array = { "foo", "bar", "baz", "and", "another", "string" };
array[array.length - 1] = new Object() {
@Override
public String toString() {
Collections.shuffle(Arrays.asList(array));
return "string";
}
};
doThing(new ArrayList<String>() {
@Override public Object[] toArray() {
return array;
}
});
}
public static void doThing(List<String> strs) {
List<String> newStrs = new ArrayList<>(strs);
System.out.println("made a safe copy " + newStrs);
for(int i = 0; i < 10; i++) {
System.out.println(newStrs);
}
}
made a safe copy [foo, bar, baz, and, another, string]
[bar, and, string, string, another, foo]
[and, baz, bar, string, string, string]
[another, baz, and, foo, bar, string]
[another, bar, and, foo, string, and]
[another, baz, string, another, and, foo]
[string, and, another, foo, string, foo]
[baz, string, foo, and, baz, string]
[bar, another, string, and, another, baz]
[bar, string, foo, string, baz, and]
[bar, string, bar, another, and, foo]
如您所见,期待
List<String>
不保证实际获得 String
的列表实例。由于类型删除和原始类型,列表实现方面甚至无法修复。另一件事,你可以责怪
ArrayList
的构造函数,是对传入集合 toArray
的信任执行。 TreeMap
不会以同样的方式受到影响,只是因为传递数组没有这样的性能增益,就像在 ArrayList
的构造中一样.这两个类都不能保证构造函数中的保护。通常,尝试编写代码并假设每个角落都有恶意代码是没有意义的。它可以做的事情太多了,以防万一。这种保护仅对真正封装了可能使恶意调用者访问某些内容的操作的代码有用,如果没有此代码,它就无法访问。
如果您需要特定代码的安全性,请使用
public static void doThing(List<String> strs) {
String[] content = strs.toArray(new String[0]);
List<String> newStrs = new ArrayList<>(Arrays.asList(content));
System.out.println("made a safe copy " + newStrs);
for(int i = 0; i < 10; i++) {
System.out.println(newStrs);
}
}
然后,您可以确定
newStrs
只包含字符串,构造后不能被其他代码修改。或使用
List<String> newStrs = List.of(strs.toArray(new String[0]));
使用 Java 9 或更高版本请注意,Java 10 的
List.copyOf(strs)
做同样的事情,但它的文档没有说明它保证不信任传入集合的 toArray
方法。所以调用List.of(…)
,如果它返回基于数组的列表,肯定会进行复制,更安全。由于没有调用者可以改变方式,数组工作,将传入的集合转储到一个数组中,然后用它填充新的集合,将始终使副本安全。由于集合可以持有对返回数组的引用,如上所示,它可以在复制阶段更改它,但不会影响集合中的副本。
因此,任何一致性检查都应该在从数组中检索到特定元素之后或在结果集合上作为一个整体进行。
关于java - 如何安全地复制 Collection ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60601540/