Jaka jest różnica między „E”, „T” i „?” dla generycznych Java?

261

Natrafiam na kod Java w następujący sposób:

public interface Foo<E> {}

public interface Bar<T> {}

public interface Zar<?> {}

Jaka jest różnica między wszystkimi trzema powyższymi i jak nazywają ten typ deklaracji klasy lub interfejsu w Javie?

as
źródło
1
Wątpię, żeby była jakaś różnica. Myślę, że to tylko nazwa tego parametru typu. A czy ostatni jest nawet ważny?
CodesInChaos

Odpowiedzi:

231

Cóż, nie ma różnicy między pierwszymi dwoma - po prostu używają różnych nazw dla parametru type ( Elub T).

Trzecia nie jest prawidłową deklaracją - ?jest używana jako symbol wieloznaczny, który jest używany, gdy podaje się argument typu , np. List<?> foo = ...Oznacza, że fooodnosi się do listy jakiegoś typu, ale nie wiemy co.

Wszystko to jest ogólne , co jest dość dużym tematem. Możesz dowiedzieć się o tym za pomocą następujących zasobów, chociaż oczywiście dostępnych jest więcej:

Jon Skeet
źródło
1
Wygląda na to, że link do pliku PDF jest uszkodzony. Znalazłem tutaj kopię , która wydaje się być kopią , ale nie mogę być w 100% pewien, ponieważ nie wiem, jak wyglądał oryginał.
John
2
@John: Tak, to jest to. Zredaguje link, czy to ten, czy Oracle ...
Jon Skeet
Czy jest coś innego niż T, E i? używane w lekach generycznych? Jeśli tak, czym one są i co one oznaczają?
sofs1
1
@ sofs1: Nie ma w tym nic specjalnego Ti E- to tylko identyfikatory. Możesz KeyValuePair<K, V>na przykład pisać . ?ma jednak szczególne znaczenie.
Jon Skeet
215

To bardziej konwencja niż cokolwiek innego.

  • T ma być typem
  • Ema być elementem ( List<E>: lista elementów)
  • Kjest kluczem (w Map<K,V>)
  • V to Wartość (jako wartość zwracana lub wartość odwzorowana)

Są one w pełni zamienne (pomimo konfliktów w tej samej deklaracji).

maniak zapadkowy
źródło
20
Litera między <> to tylko nazwa. W odpowiedzi opisujesz tylko konwencje. Nie musi to być nawet jedna wielka litera; możesz użyć dowolnej nazwy, tak jak możesz nadać klasy, zmienne itp. dowolną nazwę.
Jesper
Bardziej szczegółowy i jasny opis jest dostępny w tym artykule oracle.com/technetwork/articles/java/…
fgul
6
Nie wyjaśniłeś znaku zapytania. Doceniony.
shinzou,
129

Poprzednie odpowiedzi wyjaśniają parametry typu (T, E itp.), Ale nie wyjaśniają symboli wieloznacznych „?” Ani różnic między nimi, więc zajmę się tym.

Po pierwsze, żeby być jasnym: symbole wieloznaczne i parametry typu nie są takie same. Gdy parametry typu definiują rodzaj zmiennej (np. T), która reprezentuje typ dla zakresu, symbol wieloznaczny nie: symbol wieloznaczny po prostu określa zestaw dopuszczalnych typów, których można użyć dla typu ogólnego. Bez żadnych ograniczeń ( extendslub super), symbol wieloznaczny oznacza „użyj tutaj dowolnego typu”.

Symbol wieloznaczny zawsze znajduje się między nawiasami kątowymi i ma znaczenie tylko w kontekście typu ogólnego:

public void foo(List<?> listOfAnyType) {...}  // pass a List of any type

nigdy

public <?> ? bar(? someType) {...}  // error. Must use type params here

lub

public class MyGeneric ? {      // error
    public ? getFoo() { ... }   // error
    ...
}

Staje się bardziej mylące, gdy się pokrywają. Na przykład:

List<T> fooList;  // A list which will be of type T, when T is chosen.
                  // Requires T was defined above in this scope
List<?> barList;  // A list of some type, decided elsewhere. You can do
                  // this anywhere, no T required.

Definicje metod nakładają się na siebie. Są funkcjonalnie identyczne:

public <T> void foo(List<T> listOfT) {...}
public void bar(List<?> listOfSomething)  {...}

Jeśli więc zachodzą na siebie, po co korzystać z jednego lub drugiego? Czasami jest to po prostu styl: niektórzy twierdzą, że jeśli nie potrzebujesz parametru typu, powinieneś użyć symbolu wieloznacznego, aby kod był prostszy / bardziej czytelny. Jedną główną różnicę wyjaśniłem powyżej: parametry par definiują zmienną typu (np. T), której można użyć w innym miejscu zakresu; symbol wieloznaczny nie. W przeciwnym razie istnieją dwie duże różnice między parametrami typu a symbolem wieloznacznym:

Parametry typu mogą mieć wiele klas granicznych; symbol wieloznaczny nie może:

public class Foo <T extends Comparable<T> & Cloneable> {...}

Symbol wieloznaczny może mieć dolne granice; parametry typu nie mogą:

public void bar(List<? super Integer> list) {...}

W powyższym List<? super Integer>zdefiniowano Integerdolną granicę symbolu wieloznacznego, co oznacza, że ​​typem listy musi być liczba całkowita lub supertyp typu liczba całkowita. Granice typów ogólnych wykraczają poza to, co chcę szczegółowo omówić. Krótko mówiąc, pozwala określić, jakie typy mogą być typami ogólnymi. Dzięki temu można traktować leki generyczne polimorficznie. Np. Z:

public void foo(List<? extends Number> numbers) {...}

Można przekazać List<Integer>, List<Float>, List<Byte>, itd. Dla numbers. Bez ograniczania typów to nie zadziała - takie właśnie są ogólne.

Na koniec, oto definicja metody, która używa znaku wieloznacznego do zrobienia czegoś, czego nie sądzę, że można zrobić w inny sposób:

public static <T extends Number> void adder(T elem, List<? super Number> numberSuper) {
    numberSuper.add(elem);
}

numberSupermoże być liczbą lub dowolnym nadtypem liczby (np. List<Object>) i elemmusi być liczbą lub dowolnym podtypem. Przy wszystkich ograniczeniach kompilator może być pewien, że .add()jest bezpieczny.

Hawkeye Parker
źródło
„public void foo (List <? extends Number> numbers) {...}” czy „extends” powinno być „super”?
1a1a11a
1
Nie. Celem tego przykładu jest pokazanie podpisu, który polimorficznie obsługuje Listę Liczby i podtypy Liczby. W tym celu używasz „rozszerzeń”. Tj. „Przekaż mi Listę liczby lub cokolwiek, co rozszerza liczbę” (List <Integer>, List <Float>, cokolwiek). Taka metoda może następnie iterować listę i dla każdego elementu „e” wykonać np. E.floatValue (). Nie ma znaczenia, jaki podtyp (rozszerzenie) przekazywanej liczby - zawsze będziesz w stanie „.floatValue ()”, ponieważ .floatValue () jest metodą liczby.
Hawkeye Parker
W ostatnim przykładzie „List <? Super Number>” może po prostu być „List <Number>”, ponieważ metoda nie pozwala na nic bardziej ogólnego.
jessarah
@jessarah nope. Być może mój przykład jest niejasny, ale w tym przykładzie wspominam, że adder () może przyjąć List <Object> (Object jest nadklasą Liczby). Jeśli chcesz, aby mógł to zrobić, musi mieć podpis „Lista <? Super numer>”. Właśnie o to chodzi tutaj w „super”.
Hawkeye Parker,
2
Ta odpowiedź bardzo dobrze wyjaśnia różnice między symbolami wieloznacznymi a parametrami typu, powinno być jedno dedykowane pytanie z tą odpowiedzią. Ostatnio sięgam głębiej w generics i ta odpowiedź bardzo mi pomogła w zestawieniu, wiele precyzyjnych informacji w skrócie, dzięki!
Testo Testini
27

Zmienna typu, <T>, może być dowolnym nie-pierwotnym typem, który określisz: dowolnym typem klasy, dowolnym typem interfejsu, dowolnym typem tablicy, a nawet inną zmienną typu.

Najczęściej używane nazwy parametrów typu to:

  • E - Element (szeroko stosowany w ramach Java Collections Framework)
  • K - Klucz
  • N - liczba
  • T - typ
  • V - wartość

W Javie 7 dozwolone jest tworzenie instancji w następujący sposób:

Foo<String, Integer> foo = new Foo<>(); // Java 7
Foo<String, Integer> foo = new Foo<String, Integer>(); // Java 6
Uva
źródło
3

Najczęściej używane nazwy parametrów typu to:

E - Element (used extensively by the Java Collections Framework)
K - Key
N - Number
T - Type
V - Value
S,U,V etc. - 2nd, 3rd, 4th types

Zobaczysz te nazwy używane w interfejsie API Java SE

Waqas Ahmed
źródło
2

kompilator wykona przechwytywanie dla każdego znaku wieloznacznego (np. znaku zapytania na liście), gdy utworzy funkcję taką jak:

foo(List<?> list) {
    list.put(list.get()) // ERROR: capture and Object are not identical type.
}

Jednak ogólny typ, taki jak V, byłby w porządku i czyniąc go ogólną metodą :

<V>void foo(List<V> list) {
    list.put(list.get())
}
Tiina
źródło