Tại sao Bộ sưu tập Java không xóa các phương thức chung chung?


144

Tại sao không phải là Collection.remove (Object o) chung chung?

Có vẻ như Collection<E>có thể cóboolean remove(E o);

Sau đó, khi bạn vô tình cố gắng loại bỏ (ví dụ) Set<String>thay vì từng Chuỗi riêng lẻ từ a Collection<String>, đó sẽ là lỗi thời gian biên dịch thay vì vấn đề gỡ lỗi sau này.


13
Điều này thực sự có thể cắn bạn khi bạn kết hợp nó với autoboxing. Nếu bạn cố xóa một cái gì đó khỏi Danh sách và bạn truyền cho nó một Số nguyên thay vì int thì nó gọi phương thức remove (Object).
ScArcher2

2
Câu hỏi tương tự liên quan đến Bản đồ: stackoverflow.com/questions/857420/
Kẻ

Câu trả lời:


73

Josh Bloch và Bill Pugh đề cập đến vấn đề này trong Java Puzzlers IV: The Phantom Reference Menace, Attack of the Clone và Revenge of The Shift .

Josh Bloch nói (6:41) rằng họ đã cố gắng tạo ra phương pháp lấy bản đồ, loại bỏ phương thức này và một số phương pháp khác, nhưng "đơn giản là nó không hoạt động".

Có quá nhiều chương trình hợp lý không thể khái quát nếu bạn chỉ cho phép loại chung của bộ sưu tập là loại tham số. Ví dụ được đưa ra bởi anh ta là một giao điểm của a Listcủa Numbers và a Listcủa Longs.


6
Tại sao add () có thể lấy tham số đã nhập nhưng remove () không thể vượt quá tầm hiểu biết của tôi. Josh Bloch sẽ là tài liệu tham khảo dứt khoát cho các câu hỏi về Bộ sưu tập. Đó có thể là tất cả những gì tôi nhận được mà không cần cố gắng thực hiện một bộ sưu tập tương tự và tự mình xem. :( Cảm ơn.
Chris Mazzola

2
Chris - đọc PDF Hướng dẫn Generics Java, nó sẽ giải thích tại sao.
JeeBee

42
Thật ra, nó rất đơn giản! Nếu add () lấy một đối tượng sai, nó sẽ phá vỡ tập hợp. Nó sẽ chứa những thứ mà nó không được phép! Đó không phải là trường hợp để loại bỏ () hoặc chứa ().
Kevin Bourrillion

12
Ngẫu nhiên, quy tắc cơ bản đó - sử dụng tham số loại để chỉ ngăn chặn thiệt hại thực sự cho bộ sưu tập - được tuân thủ hoàn toàn nhất quán trong toàn bộ thư viện.
Kevin Bourrillion

3
@KevinBourrillion: Tôi đã làm việc với thuốc generic trong nhiều năm (bằng cả Java và C #) mà không bao giờ nhận ra rằng quy tắc "thiệt hại thực tế" thậm chí còn tồn tại ... nhưng bây giờ tôi đã thấy nó tuyên bố trực tiếp nó hoàn toàn có ý nghĩa 100%. Cảm ơn cá !!! Ngoại trừ bây giờ tôi cảm thấy buộc phải quay lại và xem xét các triển khai của mình, để xem liệu một số phương pháp có thể và do đó nên được thoái hóa. Thở dài.
corlettk

74

remove()(trong Mapcũng như trong Collection) không chung chung vì bạn sẽ có thể chuyển trong bất kỳ loại đối tượng nào remove(). Đối tượng bị loại bỏ không phải cùng loại với đối tượng mà bạn truyền vào remove(); nó chỉ yêu cầu họ bằng nhau Từ đặc điểm kỹ thuật của remove(), remove(o)loại bỏ các đối tượng eđó (o==null ? e==null : o.equals(e))true. Lưu ý rằng không có gì yêu cầu oephải cùng loại. Điều này xuất phát từ thực tế là equals()phương thức lấy một Objecttham số, không chỉ cùng loại với đối tượng.

Mặc dù, có thể đúng là nhiều lớp đã equals()định nghĩa sao cho các đối tượng của nó chỉ có thể bằng với các đối tượng của lớp của chính nó, điều đó chắc chắn không phải luôn luôn như vậy. Ví dụ, đặc tả cho List.equals()biết hai đối tượng List bằng nhau nếu cả hai đều là List và có cùng nội dung, ngay cả khi chúng là các cách triển khai khác nhau List. Vì vậy, quay trở lại ví dụ trong câu hỏi này, có thể có một Map<ArrayList, Something>và để tôi gọi remove()với một LinkedListđối số là, và nó sẽ xóa khóa đó là một danh sách có cùng nội dung. Điều này sẽ không thể xảy ra nếu remove()chung chung và hạn chế loại đối số của nó.


1
Nhưng nếu bạn định nghĩa Bản đồ là Bản đồ <Danh sách, Thứ gì đó> (thay vì ArrayList), thì có thể xóa bằng cách sử dụng LinkedList. Tôi nghĩ rằng câu trả lời này là không chính xác.
AlikElzin-kilaka

3
Câu trả lời có vẻ đúng, nhưng không đầy đủ. Nó chỉ cho vay để hỏi tại sao quái vật họ không khái quát equals()phương pháp là tốt? Tôi có thể thấy nhiều lợi ích hơn khi gõ an toàn thay vì cách tiếp cận "tự do" này. Tôi nghĩ rằng hầu hết các trường hợp với việc triển khai hiện tại là do lỗi xâm nhập vào mã của chúng tôi thay vì về sự vui mừng tính linh hoạt mà remove()phương thức này mang lại.
kelloss

2
@kelloss: Bạn có ý nghĩa gì khi "khái quát hóa equals()phương pháp"?
newacct

5
@MattBall: "trong đó T là lớp khai báo" Nhưng không có cú pháp nào như vậy trong Java. Tphải được khai báo là tham số loại trên lớp và Objectkhông có bất kỳ tham số loại nào. Không có cách nào để có một loại đề cập đến "lớp khai báo".
newacct

3
Tôi nghĩ rằng kelloss đang nói gì nếu bình đẳng là một giao diện chung Equality<T>với equals(T other). Sau đó, bạn có thể có remove(Equality<T> o)ochỉ là một số đối tượng có thể được so sánh với một đối tượng khác T.
weston

11

Bởi vì nếu tham số loại của bạn là ký tự đại diện, bạn không thể sử dụng phương thức xóa chung.

Tôi dường như nhớ lại việc chạy vào câu hỏi này với phương thức get (Object) của Map. Phương thức get trong trường hợp này không chung chung, mặc dù vậy, nó sẽ mong đợi được truyền một đối tượng cùng loại với tham số loại đầu tiên. Tôi nhận ra rằng nếu bạn đi xung quanh Bản đồ với ký tự đại diện làm tham số loại đầu tiên, thì không có cách nào để đưa một phần tử ra khỏi Bản đồ bằng phương thức đó, nếu đối số đó là chung. Đối số ký tự đại diện không thể thực sự hài lòng, vì trình biên dịch không thể đảm bảo rằng loại này là chính xác. Tôi suy đoán rằng lý do add là chung chung là bạn dự kiến ​​sẽ đảm bảo rằng loại này là chính xác trước khi thêm nó vào bộ sưu tập. Tuy nhiên, khi xóa một đối tượng, nếu loại không chính xác thì dù sao nó cũng không khớp.

Tôi có lẽ đã không giải thích nó rất tốt, nhưng nó có vẻ hợp lý với tôi.


1
Bạn có thể giải thích về điều này một chút?
Thomas Owens

6

Ngoài các câu trả lời khác, có một lý do khác tại sao phương thức nên chấp nhận một Object, đó là các vị ngữ. Hãy xem xét các mẫu sau:

class Person {
    public String name;
    // override equals()
}
class Employee extends Person {
    public String company;
    // override equals()
}
class Developer extends Employee {
    public int yearsOfExperience;
    // override equals()
}

class Test {
    public static void main(String[] args) {
        Collection<? extends Person> people = new ArrayList<Employee>();
        // ...

        // to remove the first employee with a specific name:
        people.remove(new Person(someName1));

        // to remove the first developer that matches some criteria:
        people.remove(new Developer(someName2, someCompany, 10));

        // to remove the first employee who is either
        // a developer or an employee of someCompany:
        people.remove(new Object() {
            public boolean equals(Object employee) {
                return employee instanceof Developer
                    || ((Employee) employee).company.equals(someCompany);
        }});
    }
}

Vấn đề là đối tượng được truyền cho removephương thức có trách nhiệm xác định equalsphương thức. Xây dựng vị ngữ trở nên rất đơn giản theo cách này.


Chủ đầu tư? (Filler filler filler)
Matt R

3
Danh sách này được triển khai như yourObject.equals(developer), như được ghi lại trong Bộ sưu tập API: java.sun.com/javase/6/docs/api/java/util/ Kẻ
Hosam Aly

13
Điều này có vẻ như là một sự lạm dụng đối với tôi
RAY

7
Đó là lạm dụng vì đối tượng vị ngữ của bạn phá vỡ hợp đồng của equalsphương thức, cụ thể là đối xứng. Phương thức remove chỉ bị ràng buộc với đặc tả của nó miễn là các đối tượng của bạn đang hoàn thành thông số bằng / hashCode, vì vậy mọi thực hiện sẽ được tự do so sánh theo cách khác. Ngoài ra, đối tượng vị ngữ của bạn không triển khai .hashCode()phương thức (không thể thực hiện nhất quán bằng), do đó, lệnh gọi sẽ không bao giờ hoạt động trên bộ sưu tập dựa trên Hash (như Hashset hoặc HashMap.keys ()). Rằng nó hoạt động với ArrayList là may mắn thuần túy.
Paŭlo Ebermann

3
(Tôi không thảo luận về câu hỏi loại chung chung - điều này đã được đặt ra trước đây -, chỉ có việc bạn sử dụng bằng cho các vị từ ở đây.) Tất nhiên HashMap và Hashset đang kiểm tra mã băm và Rodset / Map đang sử dụng thứ tự các phần tử . Tuy nhiên, họ vẫn thực hiện đầy đủ Collection.remove, mà không phá vỡ hợp đồng của mình (nếu đơn đặt hàng phù hợp với bằng). Và một ArrayList đa dạng (hoặc Tóm tắt, tôi nghĩ vậy) với cuộc gọi bằng được quay vòng vẫn sẽ thực hiện đúng hợp đồng - đó là lỗi của bạn nếu nó không hoạt động như dự định, vì bạn đang phá vỡ equalshợp đồng.
Paŭlo Ebermann

5

Giả sử ta có một bộ sưu tập Cat, và một số tài liệu tham khảo đối tượng các loại Animal, Cat, SiameseCat, và Dog. Hỏi bộ sưu tập xem nó có chứa đối tượng được tham chiếu bởi Cathoặc SiameseCattham chiếu có vẻ hợp lý không. Hỏi xem nó có chứa đối tượng được Animaltham chiếu bởi tham chiếu có vẻ tinh ranh không, nhưng nó vẫn hoàn toàn hợp lý. Rốt cuộc, đối tượng trong câu hỏi có thể là một Catvà có thể xuất hiện trong bộ sưu tập.

Hơn nữa, ngay cả khi đối tượng tình cờ là một thứ gì đó không phải là một Cat, không có vấn đề gì cho biết liệu nó có xuất hiện trong bộ sưu tập hay không - chỉ cần trả lời "không, nó không". Một bộ sưu tập "kiểu tra cứu" của một số loại sẽ có thể chấp nhận một cách có ý nghĩa tham chiếu của bất kỳ siêu kiểu nào và xác định xem đối tượng có tồn tại trong bộ sưu tập hay không. Nếu tham chiếu đối tượng được truyền vào là loại không liên quan, thì không có cách nào bộ sưu tập có thể chứa nó, do đó, truy vấn theo nghĩa nào đó không có ý nghĩa (nó sẽ luôn trả lời "không"). Tuy nhiên, vì không có cách nào để hạn chế các tham số thành các kiểu con hoặc siêu kiểu, nên thực tế nhất là chấp nhận bất kỳ loại nào và trả lời "không" cho bất kỳ đối tượng nào có loại không liên quan đến bộ sưu tập.


1
"Nếu tham chiếu đối tượng được truyền vào là loại không liên quan, không có cách nào bộ sưu tập có thể chứa nó" Sai. Nó chỉ phải chứa một cái gì đó tương đương với nó; và các đối tượng của các lớp khác nhau có thể bằng nhau.
newacct

"Có vẻ tinh ranh, nhưng nó vẫn hoàn toàn hợp lý" Phải không? Hãy xem xét một thế giới trong đó một đối tượng của một loại không thể luôn luôn được kiểm tra sự bằng nhau với một đối tượng của loại khác, bởi vì loại nào có thể bằng với tham số (tương tự như cách Comparabletham số hóa cho các loại bạn có thể so sánh với). Sau đó, sẽ không hợp lý khi cho phép mọi người vượt qua một loại không liên quan.
newacct

@newacct: Có một sự khác biệt cơ bản giữa so sánh cường độ và so sánh bằng: các đối tượng đã cho ABthuộc một loại, XYcủa một loại khác, như vậy A> BX> Y. Hoặc A> YY< A, hoặc X>BB< X. Những mối quan hệ đó chỉ có thể tồn tại nếu so sánh cường độ biết về cả hai loại. Ngược lại, phương pháp so sánh đẳng thức của một đối tượng có thể đơn giản tuyên bố nó không bằng bất kỳ loại nào khác, mà không cần phải biết bất cứ điều gì về loại khác trong câu hỏi. Một đối tượng thuộc loại Catcó thể không biết liệu đó có phải là ...
supercat

... "Lớn hơn" hoặc "nhỏ hơn" một đối tượng thuộc loại FordMustang, nhưng sẽ không gặp khó khăn gì khi nói liệu nó có bằng một đối tượng như vậy không (câu trả lời, rõ ràng là "không").
supercat

4

Tôi luôn luôn nghĩ rằng điều này là do remove () không có lý do để quan tâm loại đối tượng bạn cung cấp cho nó. Bất kể đủ dễ dàng để kiểm tra xem đối tượng đó có phải là một trong những đối tượng mà Bộ sưu tập chứa hay không, vì nó có thể gọi bằng () trên bất cứ thứ gì. Cần kiểm tra loại trên add () để đảm bảo rằng nó chỉ chứa các đối tượng của loại đó.


0

Đó là một sự thỏa hiệp. Cả hai phương pháp đều có ưu điểm của chúng:

  • remove(Object o)
    • linh hoạt hơn. Ví dụ, nó cho phép lặp qua một danh sách các số và xóa chúng khỏi danh sách dài.
    • mã sử dụng tính linh hoạt này có thể được tạo ra dễ dàng hơn
  • remove(E e) mang lại nhiều loại an toàn hơn cho những gì hầu hết các chương trình muốn làm bằng cách phát hiện các lỗi tinh vi tại thời điểm biên dịch, như cố gắng xóa nhầm một số nguyên khỏi danh sách quần short.

Khả năng tương thích ngược luôn là mục tiêu chính khi phát triển API Java, do đó loại bỏ (Object o) đã được chọn vì nó giúp việc tạo mã hiện có dễ dàng hơn. Nếu khả năng tương thích ngược KHÔNG phải là vấn đề, tôi đoán các nhà thiết kế đã chọn loại bỏ (E e).


-1

Xóa không phải là một phương thức chung để mã hiện có sử dụng bộ sưu tập không chung chung sẽ vẫn biên dịch và vẫn có hành vi tương tự.

Xem http://www.ibm.com/developerworks/java/l Library / j-jtp01255.html để biết chi tiết.

Chỉnh sửa: Một người bình luận hỏi tại sao phương thức add là chung chung. [... đã xóa lời giải thích của tôi ...] Bình luận viên thứ hai trả lời câu hỏi từ firebird84 tốt hơn tôi rất nhiều.


2
Vậy thì tại sao phương thức add lại chung chung?
Bob Gettys

@ firebird84 remove (Object) bỏ qua các đối tượng sai loại, nhưng remove (E) sẽ gây ra lỗi biên dịch. Điều đó sẽ thay đổi hành vi.
noah

: nhún vai: - hành vi thời gian chạy không thay đổi; lỗi biên dịch không phải là hành vi thời gian chạy . "Hành vi" của phương thức add thay đổi theo cách này.
Jason S

-2

Một lý do khác là vì các giao diện. Dưới đây là một ví dụ để hiển thị nó:

public interface A {}

public interface B {}

public class MyClass implements A, B {}

public static void main(String[] args) {
   Collection<A> collection = new ArrayList<>();
   MyClass item = new MyClass();
   collection.add(item);  // works fine
   B b = item; // valid
   collection.remove(b); /* It works because the remove method accepts an Object. If it was generic, this would not work */
}

Bạn đang hiển thị một cái gì đó bạn có thể thoát khỏi vì remove()không phải là đồng biến. Tuy nhiên, câu hỏi đặt ra là liệu điều này có nên được cho phép hay không. ArrayList#remove()hoạt động bằng cách bình đẳng giá trị, không tham chiếu bình đẳng. Tại sao bạn mong đợi rằng một Bsẽ bằng một A? Trong ví dụ của bạn, nó thể, nhưng đó là một kỳ vọng kỳ lạ. Tôi muốn thấy bạn cung cấp một MyClassđối số ở đây.
seh

-3

Bởi vì nó sẽ phá vỡ mã (tiền Java5) hiện có. ví dụ,

Set stringSet = new HashSet();
// do some stuff...
Object o = "foobar";
stringSet.remove(o);

Bây giờ bạn có thể nói đoạn mã trên là sai, nhưng giả sử o đến từ một tập hợp các đối tượng không đồng nhất (nghĩa là nó chứa các chuỗi, số, đối tượng, v.v.). Bạn muốn xóa tất cả các kết quả khớp, đó là hợp pháp vì loại bỏ sẽ chỉ bỏ qua các chuỗi không phải vì chúng không bằng nhau. Nhưng nếu bạn loại bỏ nó (Chuỗi o), thì nó không còn hoạt động nữa.


4
Nếu tôi khởi tạo một Danh sách <Chuỗi> tôi sẽ chỉ có thể gọi List.remove (someString); Nếu tôi cần hỗ trợ khả năng tương thích ngược, tôi sẽ sử dụng Danh sách thô - Danh sách <?> Sau đó tôi có thể gọi list.remove (someObject), không?
Chris Mazzola

5
Nếu bạn thay thế "loại bỏ" bằng "thêm" thì mã đó sẽ bị phá vỡ bởi những gì thực sự được thực hiện trong Java 5.
DJClayworth
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.