Java: ograniczone symbole wieloznaczne czy parametr typu ograniczonego?

83

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?

Tony Le
źródło
? jest zapisem skrótowym; i tak kompilator wewnętrznie zastępuje go parametrem typu; gdy wystąpi błąd kompilatora, zobaczysz parametr typu zastępczego zamiast?
niezaprzeczalny
9
korekta: mój poprzedni komentarz jest NIEPRAWIDŁOWY. symbol wieloznaczny jest bardziej wyrafinowany niż myślałem.
niezaprzeczalny
@irreputable, chociaż jest to rzeczywiście bardziej skomplikowane, twój punkt dotyczący zastąpienia go parametrem typu jest całkowicie poprawny; to tylko taka, której nie możesz zadeklarować.
Eugene,
jeśli spojrzymy tylko na te dwie metody takimi, jakie są - nie ma różnicy i jest tylko kwestia stylu; w przeciwnym razie (jeśli dodasz więcej ogólnych parametrów), sytuacja się zmieni
Eugene,

Odpowiedzi:

134

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> shapesi T shape, dlatego możemy bezpiecznie shapes.add(shape). Jeśli zostało zadeklarowane List<? extends Shape>, NIE możesz bezpiecznie adddo niego wejść (ponieważ możesz mieć a List<Square>i Circle).

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

Jaka jest różnica między powiązaniem z symbolem wieloznacznym a powiązaniem z parametrem typu?

Symbol wieloznaczny może mieć tylko jedno ograniczenie, podczas gdy parametr typu może mieć kilka ograniczeń. Symbol wieloznaczny może mieć dolną lub górną granicę, podczas gdy nie ma czegoś takiego jak dolna granica dla parametru typu.

Granice symboli wieloznacznych i granice parametrów typu są często mylone, ponieważ oba nazywane są granicami i mają po części podobną składnię. […]

Składnia :

  type parameter bound     T extends Class & Interface1 & … & InterfaceN

  wildcard bound  
      upper bound          ? extends SuperType
      lower bound          ? super   SubType

Symbol wieloznaczny może mieć tylko jedną granicę, dolną lub górną. Lista granic symboli wieloznacznych jest niedozwolona.

W przeciwieństwie do parametru typu może mieć kilka granic, ale nie ma czegoś takiego jak dolna granica dla parametru typu.

Cytaty z Effective Java 2nd Edition, pozycja 28: Użyj ograniczonych symboli wieloznacznych, aby zwiększyć elastyczność interfejsu API :

Aby uzyskać maksymalną elastyczność, używaj symboli wieloznacznych w parametrach wejściowych, które reprezentują producentów lub konsumentów. […] PECS oznacza producenta extends, konsumenta super[…]

Nie używaj symboli wieloznacznych jako typów zwracanych . Zamiast zapewnić użytkownikom dodatkową elastyczność, zmusiłoby ich to do używania symboli wieloznacznych w kodzie klienta. Prawidłowo używane typy symboli wieloznacznych są prawie niewidoczne dla użytkowników klasy. Powodują, że metody akceptują parametry, które powinny zaakceptować, i odrzucają te, które powinny odrzucić. Jeśli użytkownik klasy musi pomyśleć o typach symboli wieloznacznych, prawdopodobnie jest coś nie tak z interfejsem API klasy .

Stosując zasadę PECS, możemy teraz wrócić do naszego addIfPrettyprzykładu i uczynić go bardziej elastycznym, pisząc:

public <T extends Shape> void addIfPretty(List<? super T> list, T shape) { … }

Teraz możemy addIfPrettypowiedzieć a Circledo a List<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


Podsumowanie

  • Używaj ograniczonych parametrów typu / symboli wieloznacznych, zwiększają one elastyczność interfejsu API
  • Jeśli typ wymaga kilku parametrów, nie masz innego wyboru, jak tylko użyć parametru typu związanego
  • jeśli typ wymaga dolnej granicy, nie masz innego wyboru, jak tylko użyć ograniczonego symbolu wieloznacznego
  • „Producenci” mają górne granice, „konsumenci” mają niższe
  • Nie używaj symboli wieloznacznych w typach zwracanych
smary wielogenowe
źródło
Dziękuję bardzo, twoje wyjaśnienie jest bardzo jasne i pouczające
Tony Le
1
@Tony: Książka zawiera również krótką dyskusję na temat wyboru między parametrem wieloznacznym a parametrem typu, gdy nie ma ograniczenia. Zasadniczo, jeśli parametr typu pojawia się tylko raz w deklaracji metody, użyj symbolu wieloznacznego. Zobacz także reverse(List<?>)przykład z JLS java.sun.com/docs/books/jls/third_edition/html/…
polygenelubricants
Otrzymuję UnsupportedOperationException dla następującego kodu, <code> public static <T extends Number> void add (List <? Super T> list, T num) {list.add (num); } </code>
Samra,
1
Ponadto - nie możesz przekazać ?jako parametru metody, doWork(? type)ponieważ wtedy nie możesz użyć tego parametru w metodzie. Musisz użyć parametru typu.
Tomasz Mularczyk
1
@VivekVardhan nie możesz stworzyć takiej metody używając symboli wieloznacznych, o to chodzi. Przy podejmowaniu decyzji, które w użyciu, to jest jednym z najlepszych źródeł wyjaśniających.
Eugene
6

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 .

Andrei Fierbinteanu
źródło
Myślę, że Shape s = ...powinno być T s = ..., w przeciwnym return s;razie nie powinno się kompilować.
poligenelubricants
1

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.

chammu
źródło
1

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:

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.

[…]

Używanie symboli wieloznacznych jest jaśniejsze i bardziej zwięzłe niż deklarowanie jawnych parametrów typu i dlatego powinno być preferowane, gdy tylko jest to możliwe.

[…]

Symbole wieloznaczne mają również tę zaletę, że mogą być używane poza sygnaturami metod, jako typy pól, zmienne lokalne i tablice.

Martin Maletinsky
źródło
0

Drugi sposób jest nieco bardziej szczegółowy, ale pozwala odnieść się Tdo niego:

for (T shape : shapes) {
    ...
}

To jedyna różnica, o ile rozumiem.

Nikita Rybak
źródło