Dlaczego usunięcie z TreeSet za pomocą niestandardowego komparatora nie usuwa większego zestawu elementów?

22

Korzystając zarówno z Java 8, jak i Java 11, weź pod uwagę następujące kwestie TreeSetz String::compareToIgnoreCasekomparatorem:

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

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

Kiedy próbuję usunąć dokładne elementy znajdujące się w TreeSet, to działa: wszystkie te określone są usuwane:

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

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

Jeśli jednak spróbuję usunąć więcej, niż jest w TreeSet, wywołanie w ogóle niczego nie usunie (nie jest to kolejne wywołanie, ale wywołane zamiast fragmentu powyżej):

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

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

Co ja robię źle? Dlaczego tak się zachowuje?

Edycja: String::compareToIgnoreCasejest poprawnym komparatorem:

(l, r) -> l.compareToIgnoreCase(r)
Nikolas
źródło
5
Podobne wpis bug: bugs.openjdk.java.net/browse/JDK-8180409 (TreeSet removeAll zachowanie niezgodne z String.CASE_INSENSITIVE_ORDER)
Progman
Blisko powiązane pytania i odpowiedzi .
Naman

Odpowiedzi:

22

Oto javadoc z removeAll () :

Ta implementacja określa, który jest mniejszy z tego zestawu i określonej kolekcji, wywołując metodę size na każdym z nich. Jeśli ten zestaw ma mniej elementów, implementacja wykonuje iterację po tym zestawie, sprawdzając kolejno każdy element zwracany przez iterator, aby sprawdzić, czy jest zawarty w określonej kolekcji. Jeśli jest tak zawarty, jest usuwany z tego zestawu za pomocą metody remove iteratora. Jeśli określona kolekcja ma mniej elementów, implementacja wykonuje iterację po określonej kolekcji, usuwając z tego zestawu każdy element zwracany przez iterator, używając metody remove tego zestawu.

W drugim eksperymencie jesteś w pierwszym przypadku javadoc. I tak iteruje po „java”, „c ++” itd. I sprawdza, czy są one zawarte w zestawie zwróconym przez Set.of("PYTHON", "C++"). Nie są, więc nie są usuwane. Użyj innego TreeSet, używając tego samego komparatora jako argumentu, i powinno działać dobrze. Korzystanie z dwóch różnych implementacji zestawu, z których jedna korzysta equals(), a druga komparatora, jest naprawdę niebezpieczna.

Zauważ, że został otwarty błąd dotyczący tego: [JDK-8180409] TreeSet removeWszystkie niespójne zachowanie z String.CASE_INSENSITIVE_ORDER .

JB Nizet
źródło
Czy masz na myśli, że kiedy oba zestawy miałyby te same cechy, to działa? final Set<String> subLanguages = new TreeSet<>(String::compareToIgnoreCase); subLanguages.addAll(Arrays.asList("PYTHON", "C++", "LISP")); languages.removeAll(subLanguages);
Nikolas
1
Masz przypadek „Jeśli ten zestaw ma mniej elementów”, opisany przez javadoc. Drugi przypadek to „Jeśli określona kolekcja ma mniej elementów”.
JB Nizet
8
Ta odpowiedź jest słuszna, ale jest to bardzo nieintuicyjne zachowanie. Wygląda jak wada w projektowaniu TreeSet.
Boann
Zgadzam się, ale nic na to nie poradzę.
JB Nizet
4
Oba: jest to bardzo nieintuicyjne zachowanie, które jest poprawnie udokumentowane, ale ponieważ jest nieintuicyjne i mylące, jest to również błąd projektowy, który pewnego dnia może zostać naprawiony.
JB Nizet