Dlaczego Collection.remove (Object o) nie jest ogólna?
Wydaje się, że Collection<E>
mógłboolean remove(E o);
Następnie, gdy przypadkowo spróbujesz usunąć (na przykład) Set<String>
zamiast każdego pojedynczego ciągu z a Collection<String>
, byłby to błąd czasu kompilacji zamiast późniejszego problemu z debugowaniem.
java
api
generics
collections
Chris Mazzola
źródło
źródło
Odpowiedzi:
Josh Bloch i Bill Pugh odnoszą się do tego problemu w Java Puzzlers IV: The Phantom Reference Menace, Attack of the Clone i Revenge of The Shift .
Josh Bloch mówi (6:41), że próbowali wygenerować metodę pobierania Map, metodę usuwania i kilka innych, ale „to po prostu nie działało”.
Istnieje zbyt wiele rozsądnych programów, których nie można wygenerować, jeśli zezwolisz tylko na typ ogólny kolekcji jako typ parametru. Podany przez niego przykład to przecięcie a
List
zNumber
si aList
zLong
s.źródło
remove()
(inMap
oraz inCollection
) nie jest typowe, ponieważ powinieneś być w stanie przekazać dowolny typ obiektu doremove()
. Usunięty obiekt nie musi być tego samego typu, co obiekt, do którego przekazujeszremove()
; wymaga tylko, aby byli równi. W opisieremove()
,remove(o)
usuwa obiektue
tak, że(o==null ? e==null : o.equals(e))
jesttrue
. Zauważ, że nie ma nic wymagającegoo
ie
być tego samego typu. Wynika to z faktu, żeequals()
metoda przyjmujeObject
parametr as, a nie tylko ten sam typ, co obiekt.Chociaż może być powszechnie prawdą, że wiele klas zostało
equals()
zdefiniowanych tak, że ich obiekty mogą być równe tylko obiektom swojej własnej klasy, z pewnością nie zawsze tak jest. Na przykład specyfikacja dlaList.equals()
mówi, że dwa obiekty List są równe, jeśli oba są Listami i mają tę samą zawartość, nawet jeśli są różnymi implementacjamiList
. Wracając do przykładu w tym pytaniu, możliwe jestMap<ArrayList, Something>
wywołanie a i dla mnieremove()
zLinkedList
argumentem jako, i powinno to usunąć klucz, który jest listą o tej samej zawartości. Nie byłoby to możliwe, gdybyremove()
były ogólne i ograniczały typ argumentu.źródło
equals()
metody? Mogłem dostrzec więcej korzyści z bezpieczeństwa typów zamiast tego „libertariańskiego” podejścia. Myślę, że większość przypadków z obecną implementacją dotyczy błędów dostających się do naszego kodu, a nie radości, jakąremove()
zapewnia ta metoda.equals()
metodę”?T
musi być zadeklarowany jako parametr typu w klasie iObject
nie ma żadnych parametrów typu. Nie ma sposobu, aby typ odwoływał się do „deklarującej klasy”.Equality<T>
zequals(T other)
. Wtedy mógłbyś miećremove(Equality<T> o)
io
jest to tylko przedmiot, który można porównać do innegoT
.Ponieważ jeśli parametr typu jest symbolem wieloznacznym, nie można użyć ogólnej metody usuwania.
Wydaje mi się, że natknąłem się na to pytanie z metodą Get (Object) Map. Metoda get w tym przypadku nie jest ogólna, chociaż powinna rozsądnie oczekiwać, że zostanie przekazany obiekt tego samego typu, co pierwszy parametr typu. Zdałem sobie sprawę, że jeśli przekazujesz Mapy z symbolem wieloznacznym jako pierwszym parametrem typu, nie ma sposobu, aby uzyskać element z mapy za pomocą tej metody, jeśli ten argument był ogólny. Argumenty symboli wieloznacznych nie mogą być tak naprawdę spełnione, ponieważ kompilator nie może zagwarantować, że typ jest poprawny. Spekuluję, że powodem dodania jest to, że oczekuje się, że przed dodaniem go do kolekcji należy zagwarantować, że typ jest poprawny. Jednak podczas usuwania obiektu, jeśli typ jest nieprawidłowy, i tak nie będzie pasował.
Prawdopodobnie nie wyjaśniłem tego zbyt dobrze, ale wydaje mi się to wystarczająco logiczne.
źródło
Oprócz innych odpowiedzi istnieje jeszcze jeden powód, dla którego metoda powinna akceptować an
Object
, czyli predykaty. Rozważ następujący przykład:Chodzi o to, że obiekt przekazywany do
remove
metody jest odpowiedzialny za zdefiniowanieequals
metody. Budowanie predykatów staje się w ten sposób bardzo proste.źródło
yourObject.equals(developer)
dokumentacją w Collection API: java.sun.com/javase/6/docs/api/java/util/…equals
metody, a mianowicie symetrię. Metoda remove jest powiązana ze specyfikacją tylko wtedy, gdy obiekty spełniają specyfikację equals / hashCode, więc każda implementacja mogłaby wykonać porównanie w drugą stronę. Ponadto obiekt predykatu nie implementuje.hashCode()
metody (nie może implementować konsekwentnie do równości), dlatego wywołanie remove nigdy nie będzie działać na kolekcji opartej na Hash (takiej jak HashSet lub HashMap.keys ()). To, że działa z ArrayList, to czysty przypadek.Collection.remove
, bez zrywania umowy (o ile kolejność jest zgodna z równymi). A zróżnicowana ArrayList (lub, jak sądzę, AbstractCollection) z odwróconym wywołaniem equals nadal poprawnie implementowałaby umowę - to Twoja wina, jeśli nie działa zgodnie z przeznaczeniem, ponieważ łamieszequals
umowę.Załóżmy, jeden posiada kolekcję
Cat
, a niektóre odniesienia Przedmiot typówAnimal
,Cat
,SiameseCat
iDog
. Zadawanie kolekcję, czy zawiera on przedmiotu wskazuje się przez podanieCat
lubSiameseCat
odniesienie wydaje się rozsądny. Pytanie, czy zawiera obiekt, do któregoAnimal
odnosi się odwołanie, może wydawać się podejrzane, ale nadal jest całkowicie uzasadnione. Przedmiotowy obiekt może w końcu być aCat
i może pojawić się w kolekcji.Co więcej, nawet jeśli obiekt jest czymś innym niż a
Cat
, nie ma problemu z stwierdzeniem, czy występuje w kolekcji - wystarczy odpowiedzieć „nie, nie ma”. Pewnego typu kolekcja w stylu wyszukiwania powinna być w stanie w znaczący sposób akceptować odwołania do dowolnego nadtypu i określać, czy obiekt istnieje w kolekcji. Jeśli przekazane odwołanie do obiektu jest niepowiązanego typu, nie ma możliwości, aby kolekcja mogła go zawierać, więc zapytanie jest w pewnym sensie bez znaczenia (zawsze odpowie „nie”). Niemniej jednak, ponieważ nie ma sposobu na ograniczenie parametrów do podtypów lub nadtypów, najbardziej praktyczne jest po prostu zaakceptowanie dowolnego typu i udzielenie odpowiedzi „nie” dla obiektów, których typ nie jest powiązany z typem kolekcji.źródło
Comparable
jest sparametryzowany dla typów, z którymi można porównać). Wtedy nie byłoby rozsądne pozwolić ludziom przekazać coś niepowiązanego typu.A
iB
jednego typuX
orazY
inne, takie jakA
>B
iX
>Y
. AlboA
>Y
iY
<A
, alboX
>B
iB
<X
. Relacje te mogą istnieć tylko wtedy, gdy porównania wielkości zawierają informacje o obu typach. W przeciwieństwie do tego metoda porównywania równości obiektu może po prostu zadeklarować, że jest nierówna z jakimkolwiek innym typem, bez konieczności posiadania jakiejkolwiek wiedzy na temat tego drugiego typu. Obiekt typuCat
może nie mieć pojęcia, czy to ...FordMustang
, ale nie powinno mieć trudności ze stwierdzeniem, czy jest równy takiemu obiektowi (odpowiedź oczywiście brzmi „nie”).Zawsze myślałem, że dzieje się tak, ponieważ remove () nie ma powodu, by przejmować się typem obiektu, który mu podasz. Niezależnie od tego, łatwo jest sprawdzić, czy ten obiekt jest jednym z tych, które zawiera Kolekcja, ponieważ może wywołać equals () na czymkolwiek. Konieczne jest sprawdzenie typu w add (), aby upewnić się, że zawiera tylko obiekty tego typu.
źródło
To był kompromis. Oba podejścia mają swoją zaletę:
remove(Object o)
remove(E e)
zapewnia większe bezpieczeństwo typów do tego, co większość programów chce zrobić, wykrywając subtelne błędy w czasie kompilacji, na przykład omyłkową próbę usunięcia liczby całkowitej z listy skrótów.Kompatybilność wsteczna zawsze była głównym celem podczas ewolucji API Java, dlatego wybrano opcję remove (Object o), ponieważ ułatwiło to generowanie istniejącego kodu. Gdyby kompatybilność wsteczna NIE była problemem, myślę, że projektanci wybraliby usuń (E e).
źródło
Remove nie jest metodą ogólną, więc istniejący kod korzystający z kolekcji innej niż ogólna będzie nadal kompilowany i nadal będzie miał takie samo zachowanie.
Szczegółowe informacje można znaleźć pod adresem http://www.ibm.com/developerworks/java/library/j-jtp01255.html .
Edycja: komentator pyta, dlaczego metoda dodawania jest ogólna. [... usunąłem moje wyjaśnienie ...] Drugi komentator odpowiedział na pytanie od firebird84 znacznie lepiej niż ja.
źródło
Innym powodem są interfejsy. Oto przykład, aby to pokazać:
źródło
remove()
nie jest to kowariantne. Pytanie jednak brzmi, czy powinno to być dozwolone.ArrayList#remove()
działa na zasadzie równości wartości, a nie równości odniesień. Dlaczego miałbyś oczekiwać, że aB
będzie równe aA
? W twoim przykładzie może tak być, ale to dziwne oczekiwanie. Wolałbym, żebyśMyClass
przedstawił tutaj argument.Ponieważ spowodowałoby to uszkodzenie istniejącego kodu (sprzed wersji Java5). na przykład,
Teraz możesz powiedzieć, że powyższy kod jest błędny, ale przypuśćmy, że o pochodzi z heterogenicznego zbioru obiektów (tj. Zawiera łańcuchy, liczbę, obiekty itp.). Chcesz usunąć wszystkie dopasowania, co było legalne, ponieważ remove zignorowałoby po prostu elementy niebędące ciągami, ponieważ nie były równe. Ale jeśli usuniesz (String o), to już nie działa.
źródło