Почему удаление из TreeSet с помощью специального компаратора не приведет к удалению большего набора элементов?

22

Используя Java 8 и Java 11, рассмотрите следующее TreeSetс String::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)
Nikolas
источник
5
Связанная запись об ошибке: bugs.openjdk.java.net/browse/JDK-8180409 (TreeSet removeAll несовместимое поведение с String.CASE_INSENSITIVE_ORDER)
Progman
Тесно связанные вопросы и ответы .
Наман
Связанный: stackoverflow.com/questions/18123178/…
Didier L

Ответы:

22

Вот javadoc для removeAll () :

Эта реализация определяет, какой из этого набора и указанной коллекции меньше, вызывая метод size для каждого из них. Если в этом наборе меньше элементов, то реализация выполняет итерации по этому набору, проверяя каждый элемент, возвращаемый итератором, по очереди, чтобы определить, содержится ли он в указанной коллекции. Если он так содержится, он удаляется из этого набора методом удаления итератора. Если в указанной коллекции меньше элементов, то реализация выполняет итерацию по указанной коллекции, удаляя из этого набора каждый элемент, возвращаемый итератором, используя метод удаления этого набора.

Во втором эксперименте вы находитесь в первом случае с Javadoc. Таким образом, он перебирает «java», «c ++» и т. Д. И проверяет, содержатся ли они в множестве, возвращаемом функцией 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 Низет
8
Этот ответ правильный, но это очень не интуитивное поведение. Это похоже на недостаток в дизайне TreeSet.
Boann
Я согласен, но я ничего не могу с этим поделать.
JB Низет
4
Это и то и другое: это очень неинтуитивное поведение, которое правильно задокументировано, но, будучи не интуитивным и обманчивым, это также ошибка проектирования, которая может когда-нибудь быть исправлена.
JB Низет