Mengapa tidak menghapus dari TreeSet dengan pembanding khusus menghapus set item yang lebih besar?

22

Menggunakan Java 8 dan Java 11, pertimbangkan yang berikut TreeSetdengan String::compareToIgnoreCasepembanding:

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

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

Ketika saya mencoba untuk menghapus elemen-elemen tepat yang ada di TreeSet, ia berfungsi: semua yang ditentukan dihapus:

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

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

Namun, jika saya mencoba untuk menghapus alih-alih lebih dari yang ada di dalam TreeSet, panggilan itu tidak menghapus apa-apa sama sekali (ini bukan panggilan berikutnya tetapi disebut bukan potongan di atas):

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

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

Apa yang saya lakukan salah? Mengapa itu berlaku seperti ini?

Sunting: String::compareToIgnoreCaseadalah pembanding yang valid:

(l, r) -> l.compareToIgnoreCase(r)
Nikolas
sumber
5
Entri bug terkait: bugs.openjdk.java.net/browse/JDK-8180409 (TreeSet hapus semua perilaku tidak konsisten dengan String.CASE_INSENSITIVE_ORDER)
Progman
T&J terkait erat .
Naman

Jawaban:

22

Inilah javadoc dari removeAll () :

Implementasi ini menentukan mana yang lebih kecil dari set ini dan koleksi yang ditentukan, dengan menerapkan metode ukuran pada masing-masing. Jika himpunan ini memiliki lebih sedikit elemen, maka implementasi iterates atas himpunan ini, memeriksa setiap elemen dikembalikan oleh iterator pada gilirannya untuk melihat apakah itu terkandung dalam koleksi yang ditentukan. Jika sudah terkandung, itu dihapus dari set ini dengan metode penghapusan iterator. Jika koleksi yang ditentukan memiliki lebih sedikit elemen, maka implementasinya akan melebihi koleksi yang ditentukan, menghapus dari set ini setiap elemen dikembalikan oleh iterator, menggunakan metode penghapusan set ini.

Dalam percobaan kedua Anda, Anda berada di javadoc kasus pertama. Jadi iterates lebih dari "java", "c ++", dll dan memeriksa apakah mereka terkandung dalam Set dikembalikan oleh Set.of("PYTHON", "C++"). Mereka tidak, jadi mereka tidak dihapus. Gunakan TreeSet lain menggunakan pembanding yang sama dengan argumen, dan itu akan berfungsi dengan baik. Menggunakan dua implementasi Set yang berbeda, satu menggunakan equals(), dan yang lainnya menggunakan komparator, memang berbahaya untuk dilakukan.

Perhatikan bahwa ada bug yang dibuka tentang ini: [JDK-8180409] TreeSet menghapus semua perilaku tidak konsisten dengan String.CASE_INSENSITIVE_ORDER .

JB Nizet
sumber
Apakah yang Anda maksudkan ketika kedua set memiliki karakteristik yang sama, itu berfungsi? final Set<String> subLanguages = new TreeSet<>(String::compareToIgnoreCase); subLanguages.addAll(Arrays.asList("PYTHON", "C++", "LISP")); languages.removeAll(subLanguages);
Nikolas
1
Anda berada dalam kasus "Jika set ini memiliki lebih sedikit elemen", dijelaskan oleh javadoc. Kasus lainnya adalah "Jika koleksi yang ditentukan memiliki lebih sedikit elemen".
JB Nizet
8
Jawaban ini benar, tetapi itu adalah perilaku yang sangat tidak intuitif. Rasanya seperti cacat dalam desain TreeSet.
Boann
Saya setuju, tetapi saya tidak bisa melakukan apa-apa tentang itu.
JB Nizet
4
Keduanya: itu adalah perilaku yang sangat tidak intuitif yang didokumentasikan dengan benar, tetapi, karena tidak intuitif dan menipu, itu juga merupakan bug desain yang mungkin, suatu hari nanti, akan diperbaiki.
JB Nizet