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


22

同时使用Java 8和Java 11,将以下内容TreeSetString::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]

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

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

System.out.println(languages);                 // [java]

但是,如果我尝试删除的内容超过了中的所有内容TreeSet,则该调用根本不会删除任何内容(这不是后续调用,而是被调用了,而不是上面的代码段):

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

System.out.println(languages);                 // [c++, java, python]

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

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

(l, r) -> l.compareToIgnoreCase(r)

5
相关的错误条目:bugs.openjdk.java.net/browse/JDK-8180409(TreeSet removeAll与String.CASE_INSENSITIVE_ORDER的行为不一致)
Progman

密切相关的问答环节
纳曼

Answers:


22

这是removeAll()的javadoc :

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

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

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


您是说这两个集合具有相同的特征时起作用吗? final Set<String> subLanguages = new TreeSet<>(String::compareToIgnoreCase); subLanguages.addAll(Arrays.asList("PYTHON", "C++", "LISP")); languages.removeAll(subLanguages);
尼古拉斯

1
您遇到的情况是“如果此集合的元素较少”,则由javadoc描述。另一种情况是“如果指定的集合具有较少的元素”。
JB Nizet

8
这个答案是正确的,但这是非常不直观的行为。感觉就像是设计的缺陷TreeSet
Boann

我同意,但是对此我无能为力。
JB Nizet

4
两者兼而有之:这是一种非常直观的行为,可以正确记录下来,但是,由于缺乏直观性和欺骗性,它还是一个设计错误,有一天可能会修复。
JB Nizet
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.