Чому вилучення з 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)

5
Пов'язана запис помилки: bugs.openjdk.java.net/browse/JDK-8180409 (TreeSet RemoveAll непослідовність з String.CASE_INSENSITIVE_ORDER)
Progman

Близький Q & .
Наман

Відповіді:


22

Ось javadoc з видалення removeAll () :

Ця реалізація визначає, яка менша частина цього набору та вказаної колекції, шляхом виклику методу розміру для кожного. Якщо цей набір має менше елементів, то реалізація повторює цей набір, перевіряючи кожен елемент, повернутий ітератором, по черзі, щоб побачити, чи міститься він у зазначеній колекції. Якщо він так міститься, він видаляється з цього набору методом видалення ітератора. Якщо вказана колекція містить менше елементів, то реалізація переходить до зазначеної колекції, видаляючи з цього набору кожен елемент, повернутий ітератором, використовуючи метод видалення цього набору.

У вашому другому експерименті ви знаходитесь в першому випадку javadoc. Таким чином, він повторюється над "java", "c ++" тощо і перевіряє, чи містяться вони у наборі, поверненому користувачем Set.of("PYTHON", "C++"). Їх немає, тому їх не знімають. Використовуйте інший TreeSet, використовуючи той же компаратор, що і аргумент, і він повинен працювати добре. Використання двох різних наборів реалізацій, однієї з використанням equals(), а іншої з використанням компаратора, дійсно небезпечно.

Зауважте, що з цього приводу відкрилася помилка: [JDK-8180409] Видалити TreeSetВсі непослідовні дії з 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 Nizet

8
Ця відповідь правильна, але це дуже неінтуїтивна поведінка. Це здається вадою в дизайні TreeSet.
Боан

Я згоден, але нічого не можу з цим зробити.
JB Nizet

4
І те й інше: це дуже неінтуїтивна поведінка, яка правильно зафіксована, але, будучи неінтуїтивною та обманюючою, це також помилка в дизайні, яка, можливо, в якийсь день може бути виправлена.
JB Nizet
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.