Jakie są powody, dla których Mapa.get (Object key) nie jest (w pełni) generycznym

Jakie są przyczyny decyzji o braku w pełni ogólnej metody get w interfejsie java.util.Map<K, V>.

Aby wyjaśnić pytanie, podpis metody jest

V get(Object key)

Zamiast

V get(K key)

I zastanawiam się dlaczego (to samo dla remove, containsKey, containsValue).

Author: h0ussni, 2009-05-13

11 answers

Jak wspomniano przez innych, powód dlaczego get(), itp. nie jest ogólna, ponieważ klucz wpisu, który pobierasz, nie musi być tego samego typu co obiekt, który przekazujesz get(); Specyfikacja metody wymaga tylko, aby były równe. Wynika to z tego, jak Metoda equals() przyjmuje obiekt jako parametr, a nie tylko ten sam typ co obiekt.

Chociaż może być powszechnie prawdą, że wiele klas ma equals() zdefiniowane tak, że jej obiekty mogą być równe tylko obiektów własnej klasy, istnieje wiele miejsc w Javie, w których tak nie jest. Na przykład Specyfikacja List.equals() mówi, że dwa obiekty listy są równe, jeśli są obiema listami i mają tę samą zawartość, nawet jeśli są różnymi implementacjami List. Wracając więc do przykładu w tym pytaniu, zgodnie ze specyfikacją metody można mieć Map<ArrayList, Something> i dla mnie wywołać {[0] } z LinkedList jako argumentem, i powinno odzyskać klucz, który jest listą z tym samym zawartość. Nie byłoby to możliwe, gdyby get() były ogólne i ograniczały swój typ argumentu.

 242
Author: newacct,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-27 14:04:59

Niesamowity programista Javy w Google, Kevin Bourrillion, napisał o tym właśnie problemie w blogu jakiś czas temu (co prawda w kontekście Set zamiast Map). Najbardziej istotne zdanie:

Równomiernie, metody Javy Collections Framework (oraz Google Biblioteka zbiorów też) nigdy ogranicz rodzaje ich parametrów z wyjątkiem sytuacji, gdy jest to konieczne, aby zapobiec kolekcja z rozbicia.

Nie jestem do końca pewien, czy się z tym Zgadzam z tą zasadą -. NET wydaje się być w porządku, wymagając odpowiedniego typu klucza, na przykład - ale warto postępować zgodnie z rozumowaniem w poście na blogu. (Wspominając o.net, warto wyjaśnić, że jednym z powodów, dla których nie jest to problem w. NET jest to, że istnieje większy problem w. NET o bardziej ograniczonej wariancji...)

 98
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-13 11:42:15

Umowa jest wyrażona w ten sposób:

Bardziej formalnie, jeśli ta mapa zawiera odwzorowanie z klucza k na wartość v taką to (klucz= = null ? k = = null : Klucz.równa się (k) ), wtedy ta metoda zwraca v; w przeciwnym razie zwraca null. (Może być co najwyżej jeden taki mapowanie.)

(moje podkreślenie)

I jako takie, pomyślne wyszukiwanie klucza zależy od implementacji metody równości przez klucz wejściowy. To nie jest koniecznie zależne od Klasa k.

 27
Author: Brian Agnew,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-13 11:34:45

"Bądź konserwatywny w tym, co robisz, bądź liberalny w tym, co akceptujesz od innych."

Sprawdzanie równości może być wykonywane niezależnie od typu; metoda equals jest zdefiniowana na klasie Object i przyjmuje dowolny Object jako parametr. Tak więc, dla równoważności klucza i operacji opartych na równoważności klucza, ma sens akceptowanie dowolnego typu Object.

Gdy mapa zwraca wartości klucza, zachowuje jak najwięcej informacji o typie, używając typu parametr.

 16
Author: erickson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-22 14:48:27

myślę, że ta część poradnika generycznego wyjaśnia sytuację (mój nacisk):

" Musisz upewnić się, że generic API nie jest nadmiernie restrykcyjne; musi Kontynuuj obsługę pierwotnej umowy API. Zastanów się jeszcze raz nad przykładami z Javy.util.Kolekcja. Pre-generic API wygląda następująco:

interface Collection { 
  public boolean containsAll(Collection c);
  ...
}

Naiwną próbą uogólnienia jest:

interface Collection<E> { 
  public boolean containsAll(Collection<E> c);
  ...
}

Chociaż jest to z pewnością bezpieczne dla typu, nie spełnia oryginalnego API kontrakt. Metoda containsAll () działa z każdym rodzajem przychodzącej kolekcji. Będzie tylko succeed, jeśli przychodząca kolekcja naprawdę zawiera tylko instancje E, ale:

  • typ statyczny przychodzącego kolekcja może się różnić, być może bo rozmówca nie zna precyzyjny Typ zbioru jest przekazywane, a może dlatego, że jest to Zbiór, gdzie S jest Podtyp E.
  • jest idealnie uprawnione do wywołania containsAll () z a collection of a inny typ. Na rutyna powinna zadziałać, zwracając false."
 11
Author: Yardena,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-14 14:16:56

Powodem jest to, że zabezpieczenie jest określone przez equals i hashCode, które są metodami na Object i obie przyjmują parametr Object. Była to wczesna wada projektowa w bibliotekach standardowych Javy. W połączeniu z ograniczeniami w systemie typów Javy, wymusza wszystko, co opiera się na równości i hashCode, aby wziąć Object.

Jedynym sposobem na posiadanie bezpiecznych dla typów tabel hashowych i równości w Javie jest unikanie Object.equals i Object.hashCode i używanie zamiennika ogólnego. Functional Java zawiera klasy typu tylko w tym celu: Hash<A> oraz Equal<A>. Owijka do HashMap<K, V> jest pod warunkiem, że przyjmuje Hash<K> i Equal<K> w swoim konstruktorze. Metody tej klasy get i contains przyjmują zatem ogólny argument typu K.

Przykład:

HashMap<String, Integer> h =
  new HashMap<String, Integer>(Equal.stringEqual, Hash.stringHash);

h.add("one", 1);

h.get("one"); // All good

h.get(Integer.valueOf(1)); // Compiler error
 5
Author: Apocalisp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-28 02:16:24

Jest jeszcze jeden ważny powód, nie można tego zrobić technicznie, ponieważ łamie mapę.

Java ma polimorficzną budowę generyczną jak <? extends SomeClass>. Oznaczone takie odniesienie może wskazywać na typ podpisany <AnySubclassOfSomeClass>. Ale polimorficzny rodzajnik sprawia, że odniesienie readonly . Kompilator pozwala używać typów generycznych tylko jako zwracającego Typ metody( jak proste gettery), ale blokuje metody, w których typem generycznym jest argument (jak zwykłe settery). Oznacza to, że jeśli napiszesz Map<? extends KeyType, ValueType>, kompilator nie pozwala na wywołanie metody get(<? extends KeyType>), a mapa będzie bezużyteczna. Jedynym rozwiązaniem jest, aby ta metoda nie była ogólna: get(Object).

 4
Author: Owheee,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-20 18:29:57

Chyba Kompatybilność wsteczna. Map (LUB HashMap) nadal wymaga wsparcia get(Object).

 1
Author: Anton Gogolev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-13 11:33:02

Patrzyłem na to i myślałem, dlaczego zrobili to w ten sposób. Nie sądzę, aby żadna z istniejących odpowiedzi wyjaśniała, dlaczego nie mogli po prostu sprawić, że nowy interfejs generyczny zaakceptuje tylko odpowiedni typ klucza. Faktycznym powodem jest to, że mimo wprowadzenia leków generycznych nie stworzyli nowego interfejsu. Interfejs mapy jest tą samą starą mapą nie-generyczną, która służy zarówno jako wersja generyczna, jak i nie-generyczna. W ten sposób, jeśli masz metodę, która akceptuje mapę niestandardową, możesz ją przekazać Map<String, Customer> i nadal by działało. W tym samym czasie umowa na get akceptuje obiekt, więc nowy interfejs powinien również obsługiwać ten kontrakt.

Moim zdaniem powinni dodać nowy interfejs i zaimplementować zarówno na istniejącej kolekcji, ale zdecydowali się na kompatybilne interfejsy, nawet jeśli oznacza to gorszy projekt dla metody get. Zauważ, że zbiory same w sobie byłyby kompatybilne z istniejącymi metodami, tylko interfejsy nie.

 1
Author: Stilgar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-26 07:34:54

Kompatybilność.

Zanim generyki były dostępne, było tylko get (Object o).

Gdyby zmienili tę metodę na get ( o), prawdopodobnie wymusiłoby to masową konserwację kodu na użytkownikach Javy tylko po to, aby działający kod ponownie się skompilował.

They could have introduced a additional method, say get_checked( o) and deprecate the old GET() method so there was a gentler transition path. Ale z jakiegoś powodu tego nie zrobiono. (Sytuacja jesteśmy teraz w tym, że musisz zainstalować narzędzia takie jak findBugs, aby sprawdzić zgodność typu między argumentem get() a zadeklarowanym typem klucza mapy.)

Argumenty odnoszące się do semantyki .equals() są fałszywe, jak sądzę. (Technicznie są poprawne, ale nadal uważam, że są fałszywe. Żaden projektant przy zdrowych zmysłach nigdy nie zrobi o1.równa się (O2) true, jeśli o1 i o2 nie mają wspólnej klasy nadrzędnej.)

 1
Author: Erwin Smout,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-01 08:47:37

Robimy teraz dużą refaktoryzację i brakowało nam tego mocno wpisanego get (), aby sprawdzić, czy nie przegapiliśmy jakiegoś get () ze starym typem.

Ale znalazłem workaround / ugly trick dla sprawdzenia czasu kompilacji: tworzenie interfejsu mapy z mocno wpisanym get, containsKey, remove... i przełącz na Javę.pakiet util twojego projektu.

Pojawią się błędy kompilacji tylko po wywołaniu get (),... przy niewłaściwych typach Wszystko inne wydaje się ok dla kompilatora (przynajmniej w eclipse kepler).

Nie zapomnij usunąć tego interfejsu po sprawdzeniu kompilacji, ponieważ nie jest to to, czego chcesz w trybie runtime.

 0
Author: henva,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-10 06:20:05