Czytam o metodach ogólnych z OracleDocGenericMethod . Jestem dość zdezorientowany porównaniem, kiedy mówi, kiedy użyć symboli wieloznacznych, a kiedy metod ogólnych. Cytat z dokumentu.
interface Collection<E> { public boolean containsAll(Collection<?> c); public boolean addAll(Collection<? extends E> c); }
Mogliśmy zamiast tego użyć metod ogólnych:
interface Collection<E> { public <T> boolean containsAll(Collection<T> c); public <T extends E> boolean addAll(Collection<T> c); // Hey, type variables can have bounds too! }
[…] To mówi nam, że argument typu jest używany do polimorfizmu; jego jedynym skutkiem jest zezwolenie na użycie różnych rzeczywistych typów argumentów w różnych miejscach wywołań. W takim przypadku należy używać symboli wieloznacznych. Symbole wieloznaczne są przeznaczone do obsługi elastycznych podtypów, co próbujemy tutaj wyrazić.
Czy nie sądzimy, że dzikie karty (Collection<? extends E> c);
są również wspierane przez pewien rodzaj polimorfizmu? Dlaczego więc użycie metody ogólnej nie jest dobre w tym?
Kontynuując, stwierdza:
Metody ogólne pozwalają na użycie parametrów typu do wyrażenia zależności między typami co najmniej jednego argumentu metody i / lub jej zwracanego typu. Jeśli nie ma takiej zależności, nie należy używać metody ogólnej.
Co to znaczy?
Przedstawili przykład
class Collections { public static <T> void copy(List<T> dest, List<? extends T> src) { ... }
[…]
Moglibyśmy napisać podpis dla tej metody w inny sposób, bez używania symboli wieloznacznych:
class Collections { public static <T, S extends T> void copy(List<T> dest, List<S> src) { ... }
Dokument zniechęca do drugiej deklaracji i promuje użycie pierwszej składni? Jaka jest różnica między pierwszą a drugą deklaracją? Wydaje się, że obaj robią to samo?
Czy ktoś może oświetlić ten obszar.
?
w ogóle musisz używać . Mógłbyś przepisać to jako `public static <T1 extends Number, T2 extends Number> void copy (List <T1> dest, List <T2> src) iw tym przypadku stało się oczywiste, co się dzieje.List
parametrze using.List<T super Integer>
nie jest poprawny i nie skompiluje się.<T extends X & Y>
-> wiele granic.Rozważmy następujący przykład z The Java Programming by James Gosling 4th wydanie poniżej, w którym chcemy scalić 2 SinglyLinkQueue:
Obie powyższe metody mają taką samą funkcjonalność. Więc co jest lepsze? Odpowiedź jest druga. Własnymi słowami autora:
„Ogólna zasada polega na używaniu symboli wieloznacznych, gdy jest to możliwe, ponieważ kod zawierający symbole wieloznaczne jest ogólnie bardziej czytelny niż kod z wieloma parametrami typu. Decydując, czy potrzebujesz zmiennej typu, zadaj sobie pytanie, czy ta zmienna typu jest używana do powiązania dwóch lub więcej parametrów, lub aby powiązać typ parametru z typem zwracanym. Jeśli odpowiedź brzmi nie, wystarczy użyć symbolu wieloznacznego. "
Uwaga: W książce podana jest tylko druga metoda, a nazwa parametru to S zamiast „T”. W książce nie ma pierwszej metody.
źródło
W pierwszym pytaniu: oznacza to, że jeśli istnieje związek między typem parametru a typem zwracanym przez metodę, użyj generycznego.
Na przykład:
Tutaj wyodrębniasz część T zgodnie z określonymi kryteriami. Jeśli T to
Long
twoje metody powrócąLong
iCollection<Long>
; rzeczywisty typ zwracany jest zależny od typu parametru, dlatego użyteczne i zalecane jest używanie typów ogólnych.Jeśli tak nie jest, możesz użyć typów symboli wieloznacznych:
W tym przykładzie, niezależnie od typu elementów w kolekcjach, będą zwracane typy
int
iboolean
.W twoich przykładach:
te dwie funkcje zwrócą wartość logiczną, niezależnie od typu elementów w kolekcjach. W drugim przypadku jest ograniczony do wystąpień podklasy E.
Drugie Pytanie:
Ten pierwszy kod umożliwia przekazanie heterogenicznego
List<? extends T> src
parametru jako parametru. Ta lista może zawierać wiele elementów różnych klas, o ile wszystkie rozszerzają klasę bazową T.gdybyś miał:
i
mógłbyś
Z drugiej strony
ograniczenie
List<S> src
do jednej konkretnej klasy S, która jest podklasą T. Lista może zawierać tylko elementy jednej klasy (w tym przypadku S) i żadnej innej klasy, nawet jeśli implementują również T. Nie byłbyś w stanie użyć mojego poprzedniego przykładu, ale możesz zrobić:źródło
List<? extends Fruit> basket = new ArrayList<? extends Fruit>();
Nie jest prawidłową składnią. Musisz utworzyć wystąpienie ArrayList bez ograniczeń.ArrayList(Collection<? extends E> c)
. Czy możesz mi wyjaśnić, dlaczego to powiedziałeś?Metoda symboli wieloznacznych jest również ogólna - możesz ją nazwać z pewnym zakresem typów.
<T>
Składnia określa nazwę zmiennej typu. Jeśli zmienna typu ma jakiekolwiek zastosowanie (np. W implementacji metody lub jako ograniczenie dla innego typu), to warto nadać jej nazwę, w przeciwnym razie można by użyć jej?
jako zmiennej anonimowej. Wygląda więc na skrót.Co więcej,
?
składni nie da się uniknąć podczas deklarowania pola:źródło
Spróbuję po kolei odpowiedzieć na Twoje pytanie.
Nie. Powodem jest to, że powiązany symbol wieloznaczny nie ma zdefiniowanego typu parametru. To jest nieznane. Wszystko, co „wie”, to to, że „zamknięcie” jest pewnego typu
E
(niezależnie od definicji). Dlatego nie może zweryfikować i uzasadnić, czy podana wartość jest zgodna z typem ograniczonym.Tak więc nie ma sensu mieć zachowań polimorficznych na symbolach wieloznacznych.
Pierwsza opcja jest lepsza w tym przypadku, ponieważ
T
jest zawsze ograniczona i nasource
pewno będzie miała wartości (niewiadomych), które podklasyT
.Więc załóżmy, że chcesz skopiować całą listę numerów, pierwszą opcją będzie
src
W istocie, można przyjąćList<Double>
,List<Float>
itp, jak istnieje górna granica do parametryzowane rodzaj znaleźć wdest
.Druga opcja zmusi cię do wiązania
S
dla każdego typu, który chcesz skopiować, tak jak toAs
S
jest typem sparametryzowanym, który wymaga powiązania.Mam nadzieję, że to pomoże.
źródło
<S extends T>
stwierdza, żeS
jest to typ sparametryzowany, który jest podklasą klasyT
, więc wymaga sparametryzowanego typu (bez symbolu wieloznacznego), który jest podklasą klasyT
.Jeszcze jedna różnica, która nie została tutaj wymieniona.
Ale poniższe spowoduje błąd czasu kompilacji.
źródło
O ile rozumiem, istnieje tylko jeden przypadek użycia, w którym symbol wieloznaczny jest ściśle potrzebny (tj. Może wyrazić coś, czego nie można wyrazić za pomocą jawnych parametrów typu). To wtedy musisz określić dolną granicę.
Oprócz tego symbole wieloznaczne służą do pisania bardziej zwięzłego kodu, jak opisano w poniższych stwierdzeniach w wymienionym dokumencie:
źródło
Głównie -> Symbole wieloznaczne wymuszają typy ogólne na poziomie parametru / argumentu metody innej niż generyczna. Uwaga. Można to również domyślnie wykonać w genericMethod, ale tutaj zamiast? możemy użyć samego T.
pakiet generics;
SO Wildcard ma swoje specyficzne zastosowania, takie jak ten.
źródło