为什么不使用自定义比较器从 TreeSet 中删除更大的项目集?

Nik*_*las 21 java comparator treeset java-8 java-11

同时使用 Java 8 和 Java 11,请考虑以下TreeSet带有String::compareToIgnoreCase比较器的内容:

final Set<String> languages = new TreeSet<>(String::compareToIgnoreCase);
languages.add("java");
languages.add("c++");
languages.add("python");

System.out.println(languages);                 // [c++, java, python]
Run Code Online (Sandbox Code Playgroud)

当我尝试删除 中存在的确切元素时TreeSet,它起作用了:所有指定的元素都被删除:

languages.removeAll(Arrays.asList("PYTHON", "C++"));

System.out.println(languages);                 // [java]
Run Code Online (Sandbox Code Playgroud)

但是,如果我尝试删除而不是 中存在的更多TreeSet,则调用根本不会删除任何内容(这不是后续调用,而是调用而不是上面的代码段):

languages.removeAll(Arrays.asList("PYTHON", "C++", "LISP"));

System.out.println(languages);                 // [c++, java, python]
Run Code Online (Sandbox Code Playgroud)

我究竟做错了什么?为什么会这样?

编辑:String::compareToIgnoreCase是一个有效的比较器:

(l, r) -> l.compareToIgnoreCase(r)
Run Code Online (Sandbox Code Playgroud)

JB *_*zet 21

这是removeAll()的 javadoc :

此实现通过对每个集合调用 size 方法来确定此集合和指定集合中的较小者。如果此集合的元素较少,则实现将遍历此集合,依次检查迭代器返回的每个元素以查看它是否包含在指定的集合中。如果包含,则使用迭代器的 remove 方法将其从该集合中删除。如果指定集合的​​元素较少,则实现将迭代指定的集合,使用此集合的 remove 方法从此集合中删除迭代器返回的每个元素。

在您的第二个实验中,您处于 javadoc 的第一个案例中。因此它遍历“java”、“c++”等,并检查它们是否包含在由Set.of("PYTHON", "C++"). 他们不是,所以他们不会被删除。使用与参数相同的比较器使用另一个 TreeSet,它应该可以正常工作。使用两种不同的 Set 实现,一种使用equals(),另一种使用比较器,确实是一件危险的事情。

请注意,有一个关于此的错误:[JDK-8180409] TreeSet removeAll 与 String.CASE_INSENSITIVE_ORDER 不一致的行为

  • 这个答案是正确的,但却是非常不直观的行为。感觉像是“TreeSet”设计中的一个缺陷。 (8认同)
  • 两者兼而有之:这是一种非常不直观的行为,但却得到了正确的记录,但是,由于不直观和欺骗性,它也是一个设计错误,有一天可能会被修复。 (5认同)