Ostatnio czytałem ten artykuł: http://download.oracle.com/javase/tutorial/extra/generics/wildcards.html
Moje pytanie brzmi, zamiast tworzyć taką metodę:
public void drawAll(List<? extends Shape> shapes){
for (Shape s: shapes) {
s.draw(this);
}
}
Mogę utworzyć taką metodę i działa dobrze:
public <T extends Shape> void drawAll(List<T> shapes){
for (Shape s: shapes) {
s.draw(this);
}
}
Który sposób powinienem użyć? Czy symbol wieloznaczny jest przydatny w tym przypadku?
java
api
generics
bounded-wildcard
Tony Le
źródło
źródło
Odpowiedzi:
To zależy od tego, co musisz zrobić. Musisz użyć parametru typu bounded, jeśli chcesz zrobić coś takiego:
public <T extends Shape> void addIfPretty(List<T> shapes, T shape) { if (shape.isPretty()) { shapes.add(shape); } }
Tutaj mamy a
List<T> shapes
iT shape
, dlatego możemy bezpiecznieshapes.add(shape)
. Jeśli zostało zadeklarowaneList<? extends Shape>
, NIE możesz bezpiecznieadd
do niego wejść (ponieważ możesz mieć aList<Square>
iCircle
).Tak więc nadając nazwę ograniczonemu parametrowi typu, mamy możliwość użycia go w innym miejscu naszej metody ogólnej. Oczywiście ta informacja nie zawsze jest wymagana, więc jeśli nie musisz wiedzieć zbyt wiele o typie (np. Twój
drawAll
), wystarczy tylko symbol wieloznaczny.Nawet jeśli nie odwołujesz się ponownie do parametru typu ograniczonego, parametr typu ograniczonego jest nadal wymagany, jeśli masz wiele granic. Oto cytat z często zadawanych pytań dotyczących generycznych programów Java Angeliki Langer
Cytaty z Effective Java 2nd Edition, pozycja 28: Użyj ograniczonych symboli wieloznacznych, aby zwiększyć elastyczność interfejsu API :
Stosując zasadę PECS, możemy teraz wrócić do naszego
addIfPretty
przykładu i uczynić go bardziej elastycznym, pisząc:public <T extends Shape> void addIfPretty(List<? super T> list, T shape) { … }
Teraz możemy
addIfPretty
powiedzieć aCircle
do aList<Object>
. Jest to oczywiście bezpieczne dla komputera, a jednak nasza pierwotna deklaracja nie była wystarczająco elastyczna, aby na to pozwolić.Powiązane pytania
<? super T>
znaczy i kiedy należy go używać oraz jak ta konstrukcja ma współpracować<T>
i<? extends T>
?Podsumowanie
źródło
reverse(List<?>)
przykład z JLS java.sun.com/docs/books/jls/third_edition/html/…?
jako parametru metody,doWork(? type)
ponieważ wtedy nie możesz użyć tego parametru w metodzie. Musisz użyć parametru typu.W twoim przykładzie tak naprawdę nie musisz używać T, ponieważ nie używasz tego typu nigdzie indziej.
Ale jeśli zrobiłeś coś takiego:
public <T extends Shape> T drawFirstAndReturnIt(List<T> shapes){ T s = shapes.get(0); s.draw(this); return s; }
lub jak powiedział poligenlubricants, jeśli chcesz dopasować parametr typu na liście z innym parametrem typu:
public <T extends Shape> void mergeThenDraw(List<T> shapes1, List<T> shapes2) { List<T> mergedList = new ArrayList<T>(); mergedList.addAll(shapes1); mergedList.addAll(shapes2); for (Shape s: mergedList) { s.draw(this); } }
W pierwszym przykładzie uzyskujesz nieco większe bezpieczeństwo pisania niż zwracanie samego Shape, ponieważ możesz następnie przekazać wynik do funkcji, która może przyjąć dziecko Shape. Na przykład możesz przekazać a
List<Square>
do mojej metody, a następnie przekazać wynikowy Square do metody, która przyjmuje tylko kwadraty. Jeśli użyłeś „?” musiałbyś rzucić wynikowy Kształt na Kwadrat, co nie byłoby bezpieczne.W drugim przykładzie upewniasz się, że obie listy mają ten sam parametr typu (czego nie możesz zrobić za pomocą znaku „?”, Ponieważ każdy znak „?” Jest inny), dzięki czemu możesz utworzyć listę zawierającą wszystkie elementy z obu .
źródło
Shape s = ...
powinno byćT s = ...
, w przeciwnymreturn s;
razie nie powinno się kompilować.Rozważmy następujący przykład z The Java Programming by James Gosling 4th wydanie poniżej, gdzie chcemy połączyć 2 SinglyLinkQueue:
public static <T1, T2 extends T1> void merge(SinglyLinkQueue<T1> d, SinglyLinkQueue<T2> s){ // merge s element into d } public static <T> void merge(SinglyLinkQueue<T> d, SinglyLinkQueue<? extends T> s){ // merge s element into d }
Obie powyższe metody mają taką samą funkcjonalność. Więc co jest lepsze? Odpowiedź jest druga. Własnymi słowami autora:
„Ogólna zasada mówi o używaniu symboli wieloznacznych, kiedy 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
O ile rozumiem, symbol wieloznaczny pozwala na bardziej zwięzły kod w sytuacjach, gdy parametr typu nie jest wymagany (np. Ponieważ odwołuje się do niego w kilku miejscach lub ponieważ wymagane jest wiele granic, jak opisano szczegółowo w innych odpowiedziach).
W wskazanym przez Ciebie linku przeczytałem (w sekcji „Metody ogólne”) następujące stwierdzenia, które wskazują w tym kierunku:
źródło
Drugi sposób jest nieco bardziej szczegółowy, ale pozwala odnieść się
T
do niego:for (T shape : shapes) { ... }
To jedyna różnica, o ile rozumiem.
źródło