Tại sao không xóa khỏi TreeSet bằng bộ so sánh tùy chỉnh sẽ xóa một bộ vật phẩm lớn hơn?


22

Sử dụng cả Java 8 và Java 11, hãy xem xét các điều sau TreeSetvới bộ String::compareToIgnoreCaseso sánh:

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

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

Khi tôi cố gắng loại bỏ các yếu tố chính xác có trong TreeSet, nó hoạt động: tất cả những yếu tố được chỉ định đều bị xóa:

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

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

Tuy nhiên, nếu tôi cố gắng loại bỏ thay vì nhiều hơn hiện diện trong TreeSetcuộc gọi, cuộc gọi sẽ không xóa bất cứ điều gì cả (đây không phải là cuộc gọi tiếp theo mà được gọi thay vì đoạn trích ở trên):

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

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

Tôi đang làm gì sai? Tại sao nó hành xử theo cách này?

Chỉnh sửa: String::compareToIgnoreCaselà một so sánh hợp lệ:

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

5
Mục nhập lỗi liên quan: bug.openjdk.java.net/browse/JDK-8180409 (TreeSet removeTất cả hành vi không nhất quán với String.CASE_INSENSITIVE_ORDER)
Progman

Một câu hỏi và trả lời liên quan chặt chẽ .
Naman

Câu trả lời:


22

Đây là javadoc của remove ALL () :

Việc triển khai này xác định cái nào nhỏ hơn của bộ này và bộ sưu tập được chỉ định, bằng cách gọi phương thức kích thước trên mỗi bộ. Nếu bộ này có ít phần tử hơn, thì việc triển khai lặp lại trên bộ này, lần lượt kiểm tra từng phần tử được trình lặp trả về để xem nó có được chứa trong bộ sưu tập đã chỉ định hay không. Nếu nó được chứa như vậy, nó sẽ bị xóa khỏi tập hợp này với phương thức remove của iterator. Nếu bộ sưu tập được chỉ định có ít phần tử hơn, thì việc triển khai lặp lại trên bộ sưu tập đã chỉ định, loại bỏ khỏi bộ này từng phần tử được trả về bởi trình lặp, sử dụng phương thức loại bỏ của bộ này.

Trong thử nghiệm thứ hai của bạn, bạn thuộc trường hợp đầu tiên của javadoc. Vì vậy, nó lặp lại qua "java", "c ++", v.v. và kiểm tra xem chúng có trong Bộ trả về không Set.of("PYTHON", "C++"). Họ không phải, vì vậy họ không bị xóa. Sử dụng một TreeSet khác bằng cách sử dụng cùng một bộ so sánh làm đối số và nó sẽ hoạt động tốt. Sử dụng hai triển khai Set khác nhau, một sử dụng equals()và một khác sử dụng bộ so sánh, thực sự là một điều nguy hiểm.

Lưu ý rằng có một lỗi đã mở về điều này: [JDK-8180409] TreeSet removeTất cả hành vi không nhất quán với String.CASE_INSENSITIVE_ORDER .


Bạn có nghĩa là khi cả hai bộ sẽ có cùng đặc điểm, nó hoạt động? final Set<String> subLanguages = new TreeSet<>(String::compareToIgnoreCase); subLanguages.addAll(Arrays.asList("PYTHON", "C++", "LISP")); languages.removeAll(subLanguages);
Nikolas

1
Bạn đang ở trong trường hợp "Nếu bộ này có ít thành phần hơn", được mô tả bởi javadoc. Trường hợp khác là "Nếu bộ sưu tập được chỉ định có ít thành phần hơn".
JB Nizet

8
Câu trả lời này là đúng, nhưng nó là hành vi rất không trực quan. Nó cảm thấy như một lỗ hổng trong thiết kế TreeSet.
Boann

Tôi đồng ý, nhưng tôi không thể làm gì về điều đó.
JB Nizet

4
Đó là cả hai: đó là một hành vi rất không trực quan được ghi lại chính xác, nhưng, không trực quan và lừa dối, đó cũng là một lỗi thiết kế có thể, một ngày nào đó, có thể được sửa chữa.
JB Nizet
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.