Qual è la differenza tra 'E', 'T' e '?' per i generici Java?


261

Mi imbatto nel codice Java in questo modo:

public interface Foo<E> {}

public interface Bar<T> {}

public interface Zar<?> {}

Qual è la differenza tra tutte e tre le precedenti e come chiamano questo tipo di dichiarazioni di classe o interfaccia in Java?


1
Dubito che ci sia differenza. Immagino sia solo un nome per quel parametro di tipo. E l'ultimo è anche valido?
Codici InChaos

Risposte:


231

Beh, non c'è differenza tra i primi due: stanno solo usando nomi diversi per il parametro type ( Eo T).

La terza non è una dichiarazione valida - ?viene utilizzata come carattere jolly che viene utilizzato quando si fornisce un argomento di tipo , ad esempio List<?> foo = ...significa che si fooriferisce a un elenco di un tipo, ma non sappiamo cosa.

Tutto questo è generico , che è un argomento piuttosto vasto. Potresti desiderare di conoscerlo attraverso le seguenti risorse, anche se ovviamente ce ne sono altre disponibili:


1
Sembra che il collegamento al PDF sia interrotto. Ho trovato quella che sembra essere una copia qui , ma non posso esserne sicuro al 100% poiché non so che aspetto avesse l'originale.
Giovanni,

2
@John: Sì, è quello. Sarà modificare un collegamento in, sia che uno o Oracle uno ...
Jon Skeet

C'è qualcosa di diverso da T, E e? usato in generici? Se sì, cosa sono e cosa significano?
sofs1,

1
@ sofs1: non c'è niente di speciale in Te E- sono solo identificatori. Potresti scrivere KeyValuePair<K, V>per esempio. ?ha un significato speciale però.
Jon Skeet,

215

È più una convenzione che altro.

  • T è pensato per essere un tipo
  • Eè pensato per essere un elemento ( List<E>: un elenco di elementi)
  • Kè chiave (in a Map<K,V>)
  • V è valore (come valore di ritorno o valore mappato)

Sono completamente intercambiabili (nonostante i conflitti nella stessa dichiarazione).


20
La lettera tra <> è solo un nome. Quello che descrivi nella tua risposta sono solo convenzioni. Non deve nemmeno essere una singola lettera maiuscola; puoi usare qualsiasi nome che ti piace, proprio come puoi dare a classi, variabili ecc. qualsiasi nome ti piaccia.
Jesper,

Una descrizione più dettagliata e chiara è disponibile in questo articolo oracle.com/technetwork/articles/java/…
fgul

6
Non hai spiegato al punto interrogativo. Downvoted.
Shinzou,

129

Le risposte precedenti spiegano i parametri di tipo (T, E, ecc.), Ma non spiegano il carattere jolly "?" O le differenze tra loro, quindi lo affronterò.

Innanzitutto, per essere chiari: i parametri jolly e type non sono gli stessi. Laddove i parametri di tipo definiscono una sorta di variabile (ad esempio, T) che rappresenta il tipo per un ambito, il carattere jolly no: il carattere jolly definisce solo un set di tipi consentiti che è possibile utilizzare per un tipo generico. Senza alcun limite ( extendso super), il carattere jolly significa "usa qui qualsiasi tipo".

Il carattere jolly si trova sempre tra parentesi angolari e ha significato solo nel contesto di un tipo generico:

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

mai

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

o

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

Diventa più confuso dove si sovrappongono. Per esempio:

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.

Vi è molta sovrapposizione in ciò che è possibile con le definizioni dei metodi. I seguenti sono, funzionalmente, identici:

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

Quindi, se ci sono sovrapposizioni, perché usare l'una o l'altra? A volte, onestamente è solo stile: alcune persone lo dicono se non lo fai bisogno di un tipo di parametro, dovresti usare un carattere jolly solo per rendere il codice più semplice / più leggibile. Una delle principali differenze che ho spiegato sopra: i parametri di tipo definiscono una variabile di tipo (ad esempio, T) che puoi usare altrove nell'ambito; il carattere jolly no. Altrimenti, ci sono due grandi differenze tra i parametri di tipo e il carattere jolly:

I parametri di tipo possono avere più classi limite; il carattere jolly non può:

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

Il carattere jolly può avere limiti inferiori; i parametri di tipo non possono:

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

In quanto sopra List<? super Integer>definisce Integercome limite inferiore il carattere jolly, il che significa che il tipo Elenco deve essere Intero o un super-tipo di Numero intero. Il limite del tipo generico va oltre ciò che voglio trattare in dettaglio. In breve, ti permette di definire quali tipi può essere un tipo generico. Ciò consente di trattare i farmaci genericamente polimorficamente. Ad esempio con:

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

È possibile passare un List<Integer>, List<Float>,List<Byte> , ecc per numbers. Senza il limite di tipi, questo non funzionerà, ecco come sono generici.

Infine, ecco una definizione del metodo che utilizza il carattere jolly per fare qualcosa che non penso tu possa fare in nessun altro modo:

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

numberSuperpuò essere un Elenco di numeri o qualsiasi supertipo di Numero (ad esempio, List<Object>) e elemdeve essere Numero o qualsiasi sottotipo. Con tutti i limiti, il compilatore può essere certo che .add()is typesafe.


"public void foo (Elenco <? estende Numero> numeri) {...}" dovrebbe "estendere" essere "super"?
1a1a11a

1
No. Il punto di questo esempio è mostrare una firma che supporta polimorficamente un Elenco di numeri e i sottotipi di Numero. Per questo, si utilizza "estende". Vale a dire "passami un Elenco di numeri o qualsiasi cosa che estenda Numero" (Elenco <Intero>, Elenco <Float>, qualunque cosa). Un metodo come questo potrebbe quindi scorrere l'elenco e, per ogni elemento, "e", eseguire, ad esempio, e.floatValue (). Non importa quale sottotipo (estensione) di Numero passi - sarai sempre in grado di ".floatValue ()", perché .floatValue () è un metodo di Numero.
Hawkeye Parker,

Nel tuo ultimo esempio, "Elenco <? Super numero>" potrebbe essere semplicemente "Elenco <Numero>" poiché il metodo non consente nulla di più generico.
jessarah,

@jessarah no. Forse il mio esempio non è chiaro, ma menziono nell'esempio che adder () potrebbe prendere un List <Object> (Object è una superclasse di Number). Se vuoi che sia in grado di farlo, deve avere la firma "Elenco <? Super numero>". Questo è esattamente il punto di "super" qui.
Hawkeye Parker il

2
Questa risposta è molto buona per spiegare le differenze tra caratteri jolly e parametri di tipo, ci dovrebbe essere una domanda dedicata con questa risposta. Ultimamente sto andando più in profondità nei generici e questa risposta mi ha aiutato molto a mettere insieme le cose, molte informazioni precise in breve, grazie!
Testo Testini,

27

Una variabile di tipo, <T>, può essere qualsiasi tipo non primitivo specificato: qualsiasi tipo di classe, qualsiasi tipo di interfaccia, qualsiasi tipo di array o persino un'altra variabile di tipo.

I nomi dei parametri di tipo più comunemente usati sono:

  • E - Element (ampiamente utilizzato da Java Collections Framework)
  • K - Chiave
  • N - Numero
  • Tipo T
  • V - Valore

In Java 7 è consentito creare un'istanza in questo modo:

Foo<String, Integer> foo = new Foo<>(); // Java 7
Foo<String, Integer> foo = new Foo<String, Integer>(); // Java 6

3

I nomi dei parametri di tipo più comunemente usati sono:

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

Vedrai questi nomi usati in tutta l'API Java SE


2

il compilatore eseguirà un'acquisizione per ogni carattere jolly (ad es. punto interrogativo in Elenco) quando costituisce una funzione come:

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

Tuttavia un tipo generico come V sarebbe ok e rendendolo un metodo generico :

<V>void foo(List<V> list) {
    list.put(list.get())
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.