如何安全地复制集合?

Tom*_*ine 10 java collections copy-constructor

过去,我说过要安全地复制集合,请执行以下操作:

public static void doThing(List<String> strs) {
    List<String> newStrs = new ArrayList<>(strs);
Run Code Online (Sandbox Code Playgroud)

或者

public static void doThing(NavigableSet<String> strs) {
    NavigableSet<String> newStrs = new TreeSet<>(strs);
Run Code Online (Sandbox Code Playgroud)

但是这些“复制”构造函数,类似的静态创建方法和流,真的安全吗,规则在哪里指定?我所说的安全是指Java 语言提供的基本语义完整性保证和针对恶意调用者强制执行的集合,假设有合理的支持SecurityManager并且没有缺陷。

我很高兴与方法投掷ConcurrentModificationExceptionNullPointerExceptionIllegalArgumentExceptionClassCastException,等,或者甚至挂起。

我选择String了一个不可变类型参数的例子。对于这个问题,我对具有自身问题的可变类型集合的深拷贝不感兴趣。

(很明显,我已经看过了OpenJDK的源代码,并有某种形式的答案为ArrayListTreeSet。)

Hol*_*ger 13

对于在普通 API(例如 Collection API)中的同一 JVM 中运行的故意恶意代码,没有真正的保护。

很容易证明:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)
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);
    }
}
Run Code Online (Sandbox Code Playgroud)

如您所见,期望 aList<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);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以确定newStrs它只包含字符串,并且在构造后不能被其他代码修改。

List<String> newStrs = List.of(strs.toArray(new String[0]));与 Java 9 或更新版本一起使用
请注意,Java 10 的List.copyOf(strs)功能相同,但其文档并未声明保证不信任传入集合的toArray方法。所以调用List.of(…),它肯定会复制一份,以防它返回一个基于数组的列表,更安全。

由于没有调用者可以改变方式,数组工作,将传入的集合转储到数组中,然后用它填充新集合,将始终使副本安全。由于集合可以保存对返回数组的引用,如上所示,它可以在复制阶段更改它,但不能影响集合中的副本。

因此,任何一致性检查都应该在从数组或整个结果集合中检索到特定元素之后进行。

  • 它清楚地表明,对于所有“类似的静态创建方法和流”,没有这样的规范。因此,如果你想绝对安全,你必须自己调用 `toArray()`,因为数组不能有重写的行为,然后创建数组的集合副本,如 `new ArrayList&lt;&gt;(Arrays.asList( strs .toArray(new String[0])))` 或 `List.of(strs.toArray(new String[0]))`。两者都有强制元素类型的副作用。我个人认为他们永远不会允许“copyOf”损害不可变集合,但答案中存在替代方案。 (4认同)
  • Java 的安全模型的工作原理是向代码授予堆栈上所有代码的权限集的交集,因此当代码的调用者让您的代码执行意想不到的操作时,它仍然不会获得比最初拥有的权限更多的权限。因此,它只会让您的代码执行恶意代码在没有您的代码的情况下也可以执行的操作。您只需通过“AccessController.doPrivileged(...)”等强化您打算以提升的权限运行的代码。但是一长串与小程序安全相关的错误给了我们一个提示,为什么这项技术已被放弃...... (2认同)
  • 为什么要针对确实允许恶意收集实现溜进的特权代码来强化显然与安全无关的代码?该假设的调用者在调用您的代码之前和之后仍然会受到恶意行为的影响。它甚至不会注意到您的代码是唯一行为正确的代码。假设正确的集合实现,使用 new ArrayList&lt;&gt;(…) 作为复制构造函数是可以的。当已经为时已晚时,您没有责任解决安全问题。硬件受损怎么办?操作系统?多线程怎么样? (2认同)
  • 我并不是提倡“没有安全”,而是在正确的地方提供安全,而不是在事后尝试修复损坏的环境。这是一个有趣的主张,“*有许多集合没有正确实现其超类型*”,但它已经走得太远了,要求证明,进一步扩展这一点。原问题已得到完整解答;你现在提出的观点从来都不是其中的一部分。如前所述,“List.copyOf(strs)”在这方面不依赖于传入集合的正确性,代价显而易见。“ArrayList”是一个适合日常使用的合理折衷方案。 (2认同)