Jakie są przyczyny decyzji o braku w pełni ogólnej metody get w interfejsie java.util.Map<K, V>
.
Aby wyjaśnić pytanie, podpisem metody jest
V get(Object key)
zamiast
V get(K key)
i zastanawiam się, dlaczego (to samo remove, containsKey, containsValue
).
java
generics
collections
map
WMR
źródło
źródło
Odpowiedzi:
Jak wspomnieli inni, powód, dla którego
get()
itp. Nie jest ogólny, ponieważ klucz wpisu, który pobierasz, nie musi być tego samego typu co obiekt, do którego przekazujeszget()
; specyfikacja metody wymaga tylko, aby były one równe. Wynika to ze sposobu, w jakiequals()
metoda przyjmuje obiekt jako parametr, a nie tylko tego samego typu co obiekt.Chociaż może być prawdą, że wiele klas
equals()
zdefiniowało tak, że jej obiekty mogą być równe tylko obiektom własnej klasy, w Javie jest wiele miejsc, w których tak nie 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, zgodnie ze specyfikacją metody, możliwe jestMap<ArrayList, Something>
wywołanie a dla mnieget()
zLinkedList
argumentem as, i powinien on pobrać klucz, który jest listą o tej samej zawartości. Nie byłoby to możliwe, gdybyget()
były ogólne i ograniczyłyby typ argumentu.źródło
V Get(K k)
w C #?m.get(linkedList)
, dlaczego nie zdefiniowałeśm
typu jakoMap<List,Something>
? Nie mogę wymyślić przypadku, w którym dzwonieniem.get(HappensToBeEqual)
bez zmianyMap
typu w celu uzyskania interfejsu ma sens.TreeMap
może się nie powieść, gdy doget
metody zostaną przekazane obiekty niewłaściwego typu , ale może się ona czasami zdarzyć, np. gdy mapa jest pusta. A co gorsza, w przypadku wystąpienia problemu dostarczanyComparator
zcompare
metody (który ma sygnaturę rodzajowe!) Może być wywołana z argumentami niewłaściwego typu bez niesprawdzony ostrzeżenie. To jest zepsute zachowanie.Niesamowity programista Java w Google, Kevin Bourrillion, pisał o tym właśnie problemie w blogu jakiś czas temu (co prawda w kontekście
Set
zamiastMap
). Najważniejsze zdanie:Nie jestem do końca pewien, czy zgadzam się z tym co do zasady - .NET wydaje się być w porządku, wymagając na przykład odpowiedniego typu klucza - ale warto podążać za rozumowaniem w poście na blogu. (Wspomniawszy o .NET, warto wyjaśnić tę część powodu, dla którego nie jest to problem w .NET, ponieważ jest większy problem w .NET o bardziej ograniczonej wariancji ...)
źródło
Integer
i aDouble
nigdy nie mogą być sobie równe, to wciąż jest uczciwe pytanie, czy aSet<? extends Number>
zawiera wartośćnew Integer(5)
.Set<? extends Foo>
. Bardzo często zmieniałem kluczowy typ mapy, a potem byłem sfrustrowany, że kompilator nie może znaleźć wszystkich miejsc, w których kod wymaga aktualizacji. Naprawdę nie jestem przekonany, że jest to właściwy kompromis.Umowa jest wyrażona w następujący sposób:
(mój nacisk)
i jako takie, pomyślne wyszukiwanie klucza zależy od implementacji metody równości w kluczu wejściowym. Nie jest niekoniecznie zależy od klasy k.
źródło
hashCode()
. Bez właściwej implementacji hashCode () ładnie zaimplementowanaequals()
jest w tym przypadku raczej bezużyteczna.get()
nie trzeba brać argumentu typu,Object
aby zadowolić kontakt. Wyobraź sobie, że metoda get była ograniczona do typu kluczaK
- umowa nadal byłaby ważna. Oczywiście zastosowania, w których typ czasu kompilacji nie był podklasą, nieK
mogłyby się teraz skompilować, ale nie unieważnia to umowy, ponieważ umowy domyślnie omawiają, co się stanie, jeśli kod się skompiluje.Jest to zastosowanie prawa Postela: „bądź konserwatywny w tym, co robisz, bądź liberalny w tym, co akceptujesz od innych”.
Kontrole równości można przeprowadzać niezależnie od rodzaju;
equals
sposób określa się naObject
klasy i przyjmuje każdyObject
jako parametr. Tak więc sensowne jest, aby akceptować dowolność klucza i operacje oparte na równoważności kluczaObject
typ.Kiedy mapa zwraca kluczowe wartości, zachowuje tyle informacji o typie, ile może, używając parametru type.
źródło
V Get(K k)
w C #?V Get(K k)
w C #, ponieważ ma to również sens. Różnica między metodami Java i .NET polega na tym, że blokuje niepasujące elementy. W C # jest to kompilator, w Javie jest to kolekcja. I wściekłość o niespójnych klas kolekcji .NET jest raz na jakiś czas, aleGet()
iRemove()
tylko przyjmując typ pasujący na pewno zapobiega przypadkowemu przekazując błędną wartość w.contains : K -> boolean
.Myślę, że ta sekcja samouczka generycznego wyjaśnia sytuację (mój nacisk):
„Musisz upewnić się, że ogólny interfejs API nie jest nadmiernie restrykcyjny; musi nadal obsługiwać pierwotną umowę API. Ponownie rozważ kilka przykładów z java.util.Collection. Wstępnie ogólny interfejs API wygląda następująco:
Naiwna próba jej wygenerowania to:
Chociaż jest to z pewnością bezpieczne dla typu, nie jest zgodne z pierwotną umową API. Metoda zawieraAll () działa z każdym rodzajem kolekcji przychodzącej. Uda się to tylko wtedy, gdy przychodząca kolekcja naprawdę zawiera tylko instancje E, ale:
źródło
containsAll( Collection< ? extends E > c )
?containsAll
z ACollection<S>
, gdzieS
jest supertypem zE
. Gdyby tak było, nie byłoby to dozwolonecontainsAll( Collection< ? extends E > c )
. Ponadto, jak to wyraźnie podane w tym przykładzie jest to uzasadnione przechodzą zbierania różnych typów (o wartość powrotna jest wówczasfalse
).Powodem jest to, że ograniczenie jest określane przez
equals
ihashCode
które metody są włączoneObject
i oba przyjmująObject
parametr. To była wczesna wada projektowa w standardowych bibliotekach Java. W połączeniu z ograniczeniami w systemie typów Java, wymusza wszystko, co zależy od equals i hashCodeObject
.Jedynym sposobem, aby mieć bezpieczny typu tabele mieszania i równości w Javie jest wystrzegać
Object.equals
iObject.hashCode
i używać rodzajowe substytut. Funkcjonalna Java jest dostarczana z klasami tylko w tym celu:Hash<A>
iEqual<A>
. ZapewnioneHashMap<K, V>
jest opakowanie dla, które pobieraHash<K>
iEqual<K>
jego konstruktora. Dlatego te klasyget
icontains
metody przyjmują ogólny argument typuK
.Przykład:
źródło
Zgodność.
Zanim dostępne były leki generyczne, pojawiło się tylko (Obiekt o).
Gdyby zmienili tę metodę, aby uzyskać (<K> o), potencjalnie zmusiłoby to masową konserwację kodu do użytkowników Java, aby ponownie skompilować działający kod.
Oni mogli wprowadziliśmy dodatkowy sposób, powiedzmy get_checked (<K> O) i deprecate sposób stary get (), więc był łagodniejszy ścieżki przejścia. Ale z jakiegoś powodu tego nie zrobiono. (Obecna sytuacja polega na tym, że musisz zainstalować narzędzia takie jak findBugs, aby sprawdzić zgodność typu między argumentem get () a zadeklarowanym typem klucza <K> mapy).
Argumenty dotyczące semantyki .equals () są, jak sądzę, fałszywe. (Technicznie są poprawne, ale nadal uważam, że są fałszywe. Żaden projektant przy zdrowych zmysłach nigdy nie sprawi, że o1. Równe (o2) będą prawdziwe, jeśli o1 i o2 nie będą miały żadnej wspólnej nadklasy.)
źródło
Jest jeszcze jeden ważny powód, którego nie można zrobić technicznie, ponieważ psuje mapę.
Java ma polimorficzną ogólną budowę podobną do
<? extends SomeClass>
. Oznaczone takie odniesienie może wskazywać na typ podpisany za pomocą<AnySubclassOfSomeClass>
. Ale polimorficzny rodzajowy sprawia, że odniesienie jest tylko do odczytu . Kompilator pozwala używać typów ogólnych tylko jako zwracanej metody (jak proste metody pobierające), ale blokuje metody, w których typem ogólnym jest argument (jak zwykłe ustawiacze). Oznacza to, że jeśli napiszeszMap<? extends KeyType, ValueType>
, kompilator nie pozwala na wywołanie metodyget(<? extends KeyType>)
, a mapa będzie bezużyteczna. Jedynym rozwiązaniem jest, aby metoda ta ogólna:get(Object)
.źródło
Chyba kompatybilność wsteczna.
Map
(lubHashMap
) nadal musi wspieraćget(Object)
.źródło
put
(który ogranicza typy ogólne). Kompatybilność wsteczna jest uzyskiwana przy użyciu typów raw. Ogólne to „opt-in”.Patrzyłem na to i myślałem, dlaczego to zrobili w ten sposób. Nie sądzę, żeby którakolwiek z istniejących odpowiedzi wyjaśniała, dlaczego nie mogli po prostu sprawić, aby nowy ogólny interfejs akceptował tylko odpowiedni typ klucza. Rzeczywisty powód jest taki, że chociaż wprowadzili leki generyczne, NIE utworzyli nowego interfejsu. Interfejs mapy jest tą samą starą nie-ogólną mapą, która służy tylko jako wersja ogólna i ogólna. W ten sposób, jeśli masz metodę, która akceptuje mapę ogólną, możesz ją przekazać
Map<String, Customer>
i nadal będzie działać. Jednocześnie umowa get get przyjmuje Object, więc nowy interfejs powinien również obsługiwać tę umowę.Moim zdaniem powinni oni dodać nowy interfejs i zaimplementować oba w istniejącej kolekcji, ale zdecydowali się na kompatybilne interfejsy, nawet jeśli oznacza to gorszy projekt dla metody get. Zauważ, że same kolekcje byłyby kompatybilne z istniejącymi metodami, których nie byłyby dostępne tylko interfejsy.
źródło
Obecnie dokonujemy dużego refaktoryzacji i brakowało nam tego mocno wpisanego get (), aby sprawdzić, czy nie przegapiliśmy trochę get () ze starym typem.
Ale znalazłem obejście / brzydką sztuczkę sprawdzania czasu kompilacji: utwórz interfejs mapy z silnie wpisanym get, zawiera klucz, usuń ... i umieść go w pakiecie java.util twojego projektu.
Otrzymasz błędy kompilacji tylko przy wywołaniu get (), ... przy niewłaściwych typach, wszystko inne wydaje się w porządku dla kompilatora (przynajmniej wewnątrz keplera zaćmienia).
Nie zapomnij usunąć tego interfejsu po sprawdzeniu kompilacji, ponieważ nie jest to pożądane w środowisku wykonawczym.
źródło