Warum werden beim Entfernen aus einem TreeSet mit einem benutzerdefinierten Komparator keine größeren Elemente entfernt?

22

Beachten Sie bei Verwendung von Java 8 und Java 11 Folgendes TreeSetmit einem String::compareToIgnoreCaseKomparator:

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

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

Wenn ich versuche, die genauen Elemente in zu entfernen TreeSet, funktioniert dies: Alle angegebenen Elemente werden entfernt:

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

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

Wenn ich jedoch versuche, stattdessen mehr zu entfernen , als in der vorhanden ist TreeSet, entfernt der Aufruf überhaupt nichts (dies ist kein nachfolgender Aufruf, sondern wird anstelle des obigen Snippets aufgerufen):

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

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

Was mache ich falsch? Warum verhält es sich so?

Bearbeiten: String::compareToIgnoreCaseist ein gültiger Komparator:

(l, r) -> l.compareToIgnoreCase(r)
Nikolas
quelle
5
Zugehöriger Fehlereintrag : bugs.openjdk.java.net/browse/JDK-8180409 (TreeSet removeAll inkonsistentes Verhalten mit String.CASE_INSENSITIVE_ORDER)
Progman
Eine eng verwandte Frage & Antwort .
Naman
Siehe auch
Didier L

Antworten:

22

Hier ist das Javadoc von removeAll () :

Diese Implementierung bestimmt, welche der kleineren dieser Gruppe und der angegebenen Sammlung die kleinere ist, indem sie jeweils die Größenmethode aufruft. Wenn diese Gruppe weniger Elemente enthält, durchläuft die Implementierung diese Gruppe und überprüft nacheinander jedes vom Iterator zurückgegebene Element, um festzustellen, ob es in der angegebenen Auflistung enthalten ist. Wenn es so enthalten ist, wird es mit der Entfernungsmethode des Iterators aus diesem Satz entfernt. Wenn die angegebene Auflistung weniger Elemente enthält, durchläuft die Implementierung die angegebene Auflistung und entfernt aus dieser Gruppe jedes vom Iterator zurückgegebene Element mithilfe der Methode remove dieser Gruppe.

In Ihrem zweiten Experiment befinden Sie sich im ersten Fall des Javadoc. Es iteriert also über "Java", "C ++" usw. und prüft, ob sie in dem von zurückgegebenen Set enthalten sind Set.of("PYTHON", "C++"). Sie sind nicht, also werden sie nicht entfernt. Verwenden Sie ein anderes TreeSet mit demselben Komparator wie das Argument, und es sollte einwandfrei funktionieren. Die Verwendung von zwei verschiedenen Set-Implementierungen, von denen eine equals()und die andere einen Komparator verwendet, ist in der Tat eine gefährliche Sache.

Beachten Sie, dass hier ein Fehler aufgetreten ist : [JDK-8180409] TreeSet removeAll inkonsistentes Verhalten mit String.CASE_INSENSITIVE_ORDER .

JB Nizet
quelle
Meinst du, wenn beide Sets die gleichen Eigenschaften haben würden, funktioniert es? final Set<String> subLanguages = new TreeSet<>(String::compareToIgnoreCase); subLanguages.addAll(Arrays.asList("PYTHON", "C++", "LISP")); languages.removeAll(subLanguages);
Nikolas
1
Sie befinden sich im Fall "Wenn dieses Set weniger Elemente enthält", der vom Javadoc beschrieben wird. Der andere Fall ist "Wenn die angegebene Sammlung weniger Elemente enthält".
JB Nizet
8
Diese Antwort ist richtig, aber es ist ein sehr unintuitives Verhalten. Es fühlt sich an wie ein Fehler im Design von TreeSet.
Boann
Ich stimme zu, aber ich kann nichts dagegen tun.
JB Nizet
4
Es ist beides: Es ist ein sehr unintuitives Verhalten, das korrekt dokumentiert ist, aber da es nicht intuitiv ist und täuscht, ist es auch ein Designfehler, der eines Tages behoben werden könnte.
JB Nizet