Perché la rimozione da un TreeSet con un comparatore personalizzato non rimuove un set più grande di elementi?


22

Utilizzando sia Java 8 che Java 11, considerare quanto segue TreeSetcon un String::compareToIgnoreCasecomparatore:

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

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

Quando provo a rimuovere gli elementi esatti presenti in TreeSet, funziona: tutti quelli specificati vengono rimossi:

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

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

Tuttavia, se provo a rimuovere invece più di quanto sia presente in TreeSet, la chiamata non rimuove nulla (questa non è una chiamata successiva ma viene chiamata invece dello snippet sopra):

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

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

Che cosa sto facendo di sbagliato? Perché si comporta in questo modo?

Modifica: String::compareToIgnoreCaseè un comparatore valido:

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

5
Voce di errore correlata: bugs.openjdk.java.net/browse/JDK-8180409 (TreeSet removeTutti i comportamenti incoerenti con String.CASE_INSENSITIVE_ORDER)
Progman

Domande e risposte strettamente correlate .
Naman

Risposte:


22

Ecco il javadoc di removeAll () :

Questa implementazione determina quale è la più piccola di questa serie e della raccolta specificata, invocando il metodo size su ciascuna. Se questo set ha meno elementi, l'implementazione scorre su questo set, controllando ogni elemento restituito dall'iteratore a sua volta per vedere se è contenuto nella raccolta specificata. Se è così contenuto, viene rimosso da questo set con il metodo di rimozione dell'iteratore. Se la raccolta specificata ha meno elementi, l'implementazione scorre sulla raccolta specificata, rimuovendo da questo set ogni elemento restituito dall'iteratore, usando il metodo di rimozione di questo set.

Nel tuo secondo esperimento, sei nel primo caso del javadoc. Quindi scorre su "java", "c ++", ecc. E verifica se sono contenuti nel set restituito da Set.of("PYTHON", "C++"). Non lo sono, quindi non vengono rimossi. Utilizzare un altro TreeSet utilizzando lo stesso comparatore come argomento e dovrebbe funzionare correttamente. Usare due diverse implementazioni di Set, una usando equals()e l'altra usando un comparatore, è davvero una cosa pericolosa da fare.

Si noti che è stato aperto un bug al riguardo: [JDK-8180409] TreeSet removeTutti i comportamenti incoerenti con String.CASE_INSENSITIVE_ORDER .


Intendi quando entrambi i set avrebbero le stesse caratteristiche, funzionerà? final Set<String> subLanguages = new TreeSet<>(String::compareToIgnoreCase); subLanguages.addAll(Arrays.asList("PYTHON", "C++", "LISP")); languages.removeAll(subLanguages);
Nikolas

1
Sei nel caso "Se questo set ha meno elementi", descritto da javadoc. L'altro caso è "Se la raccolta specificata ha meno elementi".
JB Nizet

8
Questa risposta è giusta, ma è un comportamento molto poco intuitivo. Sembra un difetto nel design di TreeSet.
Boann

Sono d'accordo, ma non posso farci niente.
JB Nizet

4
È entrambi: è un comportamento non intuitivo che è documentato correttamente, ma, essendo non intuitivo e ingannevole, è anche un bug di progettazione che potrebbe essere risolto, un giorno.
JB Nizet
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.