Por que a remoção de um TreeSet com um comparador personalizado não remove um conjunto maior de itens?

22

Usando Java 8 e Java 11, considere o seguinte TreeSetcom um String::compareToIgnoreCasecomparador:

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 tento remover os elementos exatos presentes no TreeSet, ele funciona: todos os especificados são removidos:

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

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

No entanto, se eu tentar remover mais do que o presente TreeSet, a chamada não removerá nada (essa não é uma chamada subsequente, mas chamada em vez do snippet acima):

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

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

O que estou fazendo errado? Por que se comporta dessa maneira?

Editar: String::compareToIgnoreCaseé um comparador válido:

(l, r) -> l.compareToIgnoreCase(r)
Nikolas
fonte
5
Entrada de bug relacionada: bugs.openjdk.java.net/browse/JDK-8180409 (TreeSet removeAll comportamento inconsistente com String.CASE_INSENSITIVE_ORDER)
Progman
Perguntas e respostas estreitamente relacionadas .
Naman 01/01

Respostas:

22

Aqui está o javadoc de removeAll () :

Essa implementação determina qual é o menor deste conjunto e da coleção especificada, invocando o método size em cada um. Se esse conjunto tiver menos elementos, a implementação fará a iteração sobre esse conjunto, verificando cada elemento retornado pelo iterador, por sua vez, para verificar se ele está contido na coleção especificada. Se estiver contido, será removido deste conjunto com o método de remoção do iterador. Se a coleção especificada tiver menos elementos, a implementação iterará sobre a coleção especificada, removendo desse conjunto cada elemento retornado pelo iterador, usando o método de remoção desse conjunto.

Em seu segundo experimento, você está no primeiro caso do javadoc. Por isso, itera sobre "java", "c ++" etc., e verifica se estão contidos no conjunto retornado por Set.of("PYTHON", "C++"). Eles não são, portanto não são removidos. Use outro TreeSet usando o mesmo comparador que o argumento e deve funcionar bem. Usar duas implementações diferentes de Set, uma usando equals()e a outra usando um comparador, é algo perigoso de se fazer.

Observe que há um erro aberto sobre isso: [JDK-8180409] TreeSet removeAll comportamento inconsistente com String.CASE_INSENSITIVE_ORDER .

JB Nizet
fonte
Você quer dizer quando os dois conjuntos teriam as mesmas características, funciona? final Set<String> subLanguages = new TreeSet<>(String::compareToIgnoreCase); subLanguages.addAll(Arrays.asList("PYTHON", "C++", "LISP")); languages.removeAll(subLanguages);
Nikolas
11
Você está no caso "Se este conjunto tiver menos elementos", descrito pelo javadoc. O outro caso é "Se a coleção especificada tiver menos elementos".
JB Nizet 01/01
8
Esta resposta está certa, mas é um comportamento muito pouco intuitivo. Parece uma falha no design de TreeSet.
Boann
Eu concordo, mas não posso fazer nada sobre isso.
JB Nizet 01/01
4
Os dois são: é um comportamento muito pouco intuitivo que está documentado corretamente, mas, sendo não intuitivo e enganoso, também é um bug de design que pode, algum dia, ser corrigido.
JB Nizet