Per ulteriori informazioni, consultare queste domande frequenti:
http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html#SECTION1
Ecco alcuni punti importanti di queste FAQ.
Esempio (di tipo generico):
classe Coppia<X,Y> {
prima il privato X;
secondo Y privato; coppia pubblica(X a1, Y a2) {
primo = a1;
secondo = a2;
}
public X getFirst() { return first; }
public Y getSecond() { return secondo; }
public void setFirst(X arg) { first = arg; }
public void setSecond(Y arg) { secondo = arg; }
}
La classe Paio ha due parametri di tipo X E Y . Vengono sostituiti da argomenti di tipo quando il tipo generico Paio è istanziato. Ad esempio, nella dichiarazione Coppia<String, Data> il parametro del tipo X viene sostituito dall’argomento tipo Corda E Y è sostituito da Data .
L’ambito degli identificatori X E Y È l’intera definizione della classe. In questo ambito i due parametri di tipo X E Y vengono utilizzati come se fossero tipi (con alcune restrizioni). Nell'’esempio precedente, i parametri di tipo vengono utilizzati com argomento e restituiscono il tipo di metodi di stanza e i tipi di campi di istanza.
I parametri di tipo posso essere dichiarati con limiti. I limiti danno accesso ai metodi del tipo sconosciuto rappresentato dal parametro tipo. Nel nostro esempio non invochiamo alcun metodo di tipo sconosciuto X E Y . Per questo motivo due parametri di tipo sono illimitati.
Esempio (di un tipo parametrizzato concreto):
public void printPair( Coppia<String,Long> paio) {
System.out.println("("+pair.getFirst()+","+pair.getSecond()+")");
} Coppia<String,Long> limite = nuovo Coppia<String,Long> ("massimo",1024L);
stampaCoppia(limite);
Esempio (di un tipo con parametri jolly):
public void printPair( Coppia<?,?> paio) {
System.out.println("("+pair.getFirst()+","+pair.getSecond()+")");
} Coppia<?,?> limite = nuovo Coppia<String,Long> ("massimo",1024L);
stampaCoppia(limite);
Tutte le istanze di un tipo generico condividono la stessa rappresentazione del tipo di runtime, ovvero la rappresentazione del tipo non elaborato. Ad esempio, le istanziazioni di tipo generico Lista , ad esempio Elenco<Data> , Elenco<String> , Elenco<Long> , ecc. hanno tipi statici diversi in fase di compilazione, ma lo stesso tipo dinamico Lista in fase di esecuzione.
Un cast è composto da due parti:
- un controllo del tipo statico eseguito dal compilatore in fase di compilazione e
- un controllo del tipo dinamico eseguito dalla macchina virtuale in fase di runtime.
La parte statica risolve i lanci senza senso, che non possono avere successo, come il cast da Corda Quello Data o da Elenco<String> Quello Elenco<Data> .
La parte dinamica utilizza le informazioni sul tipo di runtime ed esegue un controllo del tipo di runtime. Sollevare a ClassCastException se il tipo dinamico dell’oggetto non è il tipo di destinazione (o un sottotipo del tipo di destinazione) del cast. Esempi di calchi con una parte dinamica sono il cast da Oggetto Quello Corda o da Oggetto Quello Elenco<String> . Sono i cosiddetti abbattuti, da un supertipo fino a un sottotipo.
Esempi di tipi parametrici concreti sono Elenco<String> , Mappa<String,Data> , ma no Elenco<? estende il numero> O Mappa<String,?> .
A volte ci si aspetta che a Elenco<Oggetto> sarebbe un supertipo di a Elenco<String> , Perché Oggetto è un supertipo di Corda . Questa aspettativa deriva dal fatto che esiste un tale tipo di relazione per gli array: Oggetto[] è un supertipo di Corda[] , Perché Oggetto è un supertipo di Corda . (Questo tipo di relazione è noto come covarianza .) La relazione super-sottotipo dei tipi di componenti si estende ai tipi di array corrispondenti. Non esiste una relazione di tipo di questo tipo per le istanze di tipi generici. (I tipi parametrizzati sono non covariata.)
La mancanza di una relazione super-sottotipo tra istanziazioni dello stesso tipo generico ha conseguenze diverse. Ecco un esempio.
Esempio:
void stampaTutto(ArrayList<Object> c) {
per (Oggetto o: c)
System.out.println(o);
} Elenco ArrayList<String> = nuovo ArrayList<String>();
...riempi l'elenco...
stampaTutto(lista); // errore
UN ArrayList<String> l’oggetto non può essere passato come argomento a un metodo che requestie a ElencoArray<Oggetto> perché i due tipi sono istanziazioni dello stesso tipo generico, ma for diverso argomenti di tipo, e per questo motivo non sono compatibili tra loro.
I tipi parametrici concreti sono istanze concrete di un tipo generico. Sono quasi come dei tipi; ci sono solo alcune restrizioni. NON possono essere utilizzati per i seguenti scopi:
- per la creazione di array
- nella gestione delle eccezioni
- in una classe letterale
- in un istanza di espressione
Gli array che contengono elementi il cui tipo e un tipo parametrico concreto sono illegali.
Esempio (di tipo di array illegale):
test del vuoto statico() {
Coppia<Integer,Intero>[] intPairArr = nuovo Coppia<Integer,Intero>[10] ; // errore
addElements(intPairArr);
Coppia<Integer,Intero> coppia = intPairArr[1];
Intero i = coppia.getFirst();
coppia.setSecondo(i);
}
addElementi vuoti statici( Oggetto[] objArr) {
objArr[0] = nuova Coppia<Integer,Integer>(0,0);
objArr[1] = nuova Coppia<String,String>("",""); // dovrebbe fallire con ArrayStoreException
}
Il compilatore vieta la creazione di array il cui tipo di componente è un tipo parametrico concreto, come Coppia<Integer,Intero> nel nostro esempio. Abbiamo discusso nella voce precedente il motivo per cui è ragionevole che il compilatore qualifichi a Coppia<Integer,Intero>[] come illegale. Il problema chiave è che il compilatore e il sistema runtime devono garantire che un array sia a omogeneo sequenza di elementi dello stesso tipo. Uno dei controlli del tipo, vale a dire il controllo dell’array-store eseguito dalla macchina virtuale in fase di esecuzione, non riscere rilevare l’inserimento incriminato di un elemento estraneo. Nell’esempio il secondo inserimento nel aggiungiElementi dovrebbe fallire perché stavano aggiungendo una coppia di stringhe a un array di valori integrali, ma non fallisce come previsto. Le ragioni sono state discusse nella voce precedente.
Se non possiamo utilizzare array che contenuto elementi il cui tipo è un tipo parametrico concreto, cosa utilizziamo come soluzione alternativa?
Consideriamo 3 soluzioni alternative possibili:
- serie di tipi grezzi
- array di tipo con parametri jolly illimitato
- raccolta anziché array
Sebbene le matrici dei tipi concreti con parametri siano illegali, sono consentite raccolte di tipi concreti con parametri.
Un tipo con parametri jolly è un’istanza di un tipo generico cui almeno un argomento di tipo è un carattere jolly. Esempi di tipi con parametri jolly sono Collezione<?> , Elenco<? estende il numero> , Comparatore<? super corda> E Coppia<String,?> . Un tipo con parametri jolly denota una famiglia di tipi che comprende istanze concrete di un tipo generico. Il tipo di carattere jolly utilizzato determina quali tipi parametrici concreti appartengono alla famiglia. Ad esempio, il tipo con parametri jolly Collezione<?> denota la famiglia di tutte le istanziazioni di Collezione interfaccia indipendente dall'’argomento tipo. Il tipo con parametri jolly Elenco<? estende il numero> denota la famiglia di tutti i tipi di elenco di cui il tipo di elemento è un sottotipo Numero . Il tipo con parametri jolly Comparatore<? super corda> è la famiglia di tutte le istanziazioni di Comparatore interfaccia per tipi di argomenti di tipo che sono supertipi di Corda.
Un tipo parametrizzato con caratteri jolly non è un tipo nel senso normale (diverso da una classe/interfaccia non parametrizzata o da un tipo non elaborato).
I tipi con parametri jolly possono essere utilizzati per la digitazione (come classi e interfacce non parametrizzate):
- come argomento e tipi di metodi restituiti
- come tipo di campo o variabile di riferimento locale
- come tipo di componente di un array come argomento di tipo di altri tipi parametrizzati
- come tipo di bersaglio nei cast
Non solo i tipi possono essere generici, ma anche i metodi possono essere generici. I metodi statici e non statici ausado i costruttori possono avere parametri di tipo. La sintassi per la dichiarazione formale dei parametri è simile alla sintassi dei tipi generici. La sezione del parametro di tipo è delimitata da parentesi angolari e viene visualizzata prima del tipo restituito del metodo. La sintassi e il significato sono identici a tutti gli ’elenco dei parametri di type di un tipo generico.
Ecco l’esempio di un generico massimo metodo che calcola il valore massimo in una raccolta di elementi di tipo sconosciuto UN .
Esempio (di un metodo generico):
Collezioni di classe {
pubblico statico <A estende il Paragonabile<A>> A max(Collezione< UN > xs) {
Iteratore< UN > xi = xs.iterator();
UN w = xi.successivo();
mentre (xi.hasNext()) {
UN x = xi.successivo();
if (w.compareTo(x) < 0) w = x;
}
ritorno w;
}
}
IL massimo Il metodo ha un parametro di tipo, denominato UN . È un segnaposto per il tipo di elemento della raccolta su cui funziona il metodo. Il parametro type ha un limite; deve essere un tipo di cui è un sottotipo Paragonabile<A>
Esempio (dello stesso tipo generico, questa volta con limiti):
interfaccia pubblica Paragonabile<T> {
public int compareTo(T arg);
}
classe pubblica TreeMap<Key estende il comparabile<Key> ,Dati>{
classe statica privata Entry<K,V> {
Tasto K;
valore V;
Voce<K,V> sinistra = null;
Voce<K,V> destra = null;
Voce<K,V> genitore;
}
transitorio privato Entry<Key, Data> root = null;
...
voce privata<Key,Data> getEntry(Chiave chiave) {
Voce<Key,Data> p = radice;
Chiave k = chiave;
mentre (p!= nullo) {
int cmp = k. confrontaCon(p.tasto) ;
se (cmp == 0)
restituire p;
altrimenti se (cmp < 0)
p = p.sinistra;
altro
p = p.destra;
}
restituire null;
}
public boolean contieneKey(Chiave chiave) {
return getEntry(chiave) != null;
}
...
}
Nell'’esempio precedente, il parametro tipo Chiave ha il limite Paragonabile<Key> . La specificazione di un limite ha due effetti:
- Dà accesso ai metodi specificati dal limite . Nell’esempio, il limite Paragonabile<Key> dà accesso al confrontare con metodo che vogliamo invocare nell’implementation del ns Mappa ad albero classe.
- Solo i tipi “entro i limiti” possono essere utilizzati per creare un’istanza del tipo generico. Nell’esempio, viene un tipo parametrizzato MappaAlbero<Numero,Stringa> verrebbe rifiutato, perché il tipo Numero non è un sottotipo di Paragonabile<Numero> . Un tipo parametrizzato come MappaAlbero<Stringa,Stringa> sarebbe accettato, perché il tipo String rientra nei limiti, ovvero è un sottotipo di Paragonabile<String> .
Tutte le classi, le interfacce e i tipi enum possono essere utilizzati come parametri di tipo associati, inclusi i tipi nidificati e interni. Né i tipi primitivi né i tipi array possono essere utilizzati come parametri di tipo associativo.
Esempi (di limiti dei parametri di tipo):
la classe X0 <T si estende int > { ... } // errore
la classe X1 <T si estende Oggetto[] > { ... } // errore
la classe X2 <T si estende Numero > { ... }
la classe X3 <T si estende Corda > { ... }
la classe X4 <T si estende Percorribile > { ... }
la classe X5 <T si estende Thread.State > { ... }
la classe X6 <T si estende Lista > { ... }
la classe X7 <T si estende Elenco<String> > { ... }
la classe X8 <T si estende Elenco<? estende il numero> > { ... }
la classe X9 <T si estende Paragonabile<? super Numero> > { ... }
la classe X10<T si estende Mappa.Entrata<?,?> > { ... }
L’esempio di codice mostra che i tipi primitivii come int e tipi di array come Oggetto[] non sono consentiti come parametro di tipo associato.
Tipi di classi, vieni Numero O Corda e tipi di interfaccia, come Percorribile , sono consentiti come parametro di tipo associato.
Tipi di enumerazione, ad esempio Thread.State sono consentiti anche come parametro di tipo associato. Thread.State è un esempio di tipo annidato utilizzato come parametro di tipo associato. Sono consentiti anche tipi interni non statici.
I tipi grezzi sono consentiti come limite del parametro di tipo; Lista è un esempio.
I tipi con parametri sono consentiti come limite di parametri di tipo, inclusi tipi con parametri concreti come Elenco<String> , tipi parametrici con caratteri jolly limitati come Elenco<? estende il numero> E Paragonabile<? super lungo> e tipi con parametri jolly illimitati come Mappa.Entrata<?,?> . Un limite che è un tipo con parametri jolly consente come argomento di tipo tutti i tipi che appartengono alla famiglia di tipi denotata dal carattere jolly. Il tipo associato con parametri jolly fornisce solo accesso limitato a campi e metodi; le restrizioni dipendono dal tipo di carattere jolly.
Esempio (di tipo con parametri jolly come parametro di tipo associato):
classe X< T estende List<? estende il numero> > {
public void someMethod(T t) {
t.add(new Long(0L)); // errore
Numero n = t.remove(0);
}
}
prova di classe {
public static void main(String[] args) {
X<ArrayList< Lungo >> x1 = nuovo X<ArrayList<Long>>();
X<ArrayList< Corda >> x2 = nuovo X<ArrayList<String>>(); // errore
}
}
Variabili di riferimento di tipo T (il parametro di tipo) vengono trattati come variabili di riferimento di tipo jolly (il parametro di tipo associato). Nel nostro esempio, la conseguenza è che il compilatore rifiuta l’invocazione di metodi che accettano un argomento del tipo “sconosciuto” rappresentato dal parametro di tipo, come ad esempio Elenco.add , perché il limite è un tipo con parametri jolly con un limite superiore.
Allo stesso tempo il limite Elenco<? estende il numero> determina i tipi che possono essere utilizzati come argomenti di tipo. Il compilatore accetta tutti gli argomenti di tipo che appartengono alla famiglia di tipi Elenco<? estende il numero> , cioè tutti i sottotipi di Lista con un argomento di tipo che è un sottotipo di Numero .
Un parametro di tipo può essere utilizzato come limite per un altro parametro di tipo.
Esempio (di un parametro di tipo utilizzato com parametro di tipo associato):
classe Tripla <T> {
privato T fst, snd, trd;
pubblico < U estende T , V estende T , W estende T > Tripla(U arg1, V arg2, W arg3) {
fst = arg1;
snd = arg2;
trd = arg3;
}
}
In questo esempio il parametro type T della classe con parametri viene utilizzata come limite dei parametri di tipo U , V E W di un metodo di istanza parametristoto di quella classe.
Ulteriori opportunità per l’utilizzo dei parametri di type come limiti di altri parametri di type includono situazioni in cui un tipo nidificato è definito all’interno di un generico type o una classe locale è definita all’interno di un metodo generico. È anche consentito utilizzare un parametro di tipo come limite di un altro parametro di tipo nella stessa sezione di parametro di tipo.
Un limite che corrisponde a una classe dà accesso a tutti i relativi membri pubblici, ovvero campi pubblici, metodi e tipi nidificati. Solo i costruttori non sono resi accessibili, perché non vi è alcuna garanzia che una sottoclasse del bound abbia gli stessi costruttori del bound.
Esempio (di una classe utilizzata come limite di un parametro di tipo):
classe pubblica SuperClasse { // membri statici
enum pubblica EnumType {QUESTO, QUELLO}
oggetto statico pubblico staticField;
public static void staticMethod() { ... }// membri non statici
classe pubblica Classe Interna { ... }
oggetto pubblico nonStaticField;
public void nonStaticMethod() { ... }// costruttori
pubblica SuperClasse() { ... }// membri privati
oggetto privato privateField;...
}la classe finale pubblica SomeClass<T si estende SuperClasse > {
oggetto T privato;
public SomeClass(T t) { oggetto = t; }public String toString() {
ritorno
"tipo nidificato statico : "+T.EnumType.class+"\n"
+"campo statico : "+T.staticField+"\n"
+"metodo statico : "+T.staticMethod()+"\n"
+"tipo annidato non statico: "+T.InnerClass.class+"\n"
+"campo non statico : "+object.nonStaticField+"\n"
+"metodo non statico : "+object.nonStaticMethod()+"\n"
+"costruttore : "+(new T())+"\n" // errore
+"membro privato : "+object.privateField+"\n" // errore
;
}
}
Il limite SuperClasse dà accesso ai suoi tipi nidificati, campi e metodi statici e campi e metodi non statici. Solo il costruttore non è accessibile. Questo perché i costruttori non vengono ereditati. Ogni sottoclasse definisce i propri costruttori e non ha bisogno di supportare i costruttori della sua superclasse. Quindi non vi è alcuna garanzia che una sottoclasse di SuperClasse avrà lo stesso costruttore della sua superclasse.
Il contesto in cui Enum<E estende Enum<E>> ” appare è la dichiarazione del Enum classe nel pacchetto java.lang :
classe astratta pubblica Enum<E estende Enum<E>> {
...
}
Il tipo Enum è la classe base comune a tutti i tipi di enumerazione. In Java un tipo di enumerazione come Colore viene tradotto in una classe Colore che si estende Enum<Colore> . Lo scopo della superclasse Enum è fornire funzionalità comuni a tutti i tipi di enumerazione.
Ecco uno schizzo della classe Enum :
classe astratta pubblica Enum< E estende Enum<E>> implementa Comparable< E >, serializzabile {
nome della stringa finale privata;
public final String nome() { ... } private final int ordinale;
public final int ordinal() { ... }protected Enum(Nome stringa, int ordinale) { ... }
public String toString() { ... }
public final boolean equals(Oggetto altro) { ... }
public final int hashCode() { ... }
oggetto finale protetto clone() lancia CloneNotSupportedException { ... }
public final int compareTo( E o) { ... }finale pubblica Class< E > getDeclaringClass() { ... }
public static <T estende Enum<T>> T valueOf(Class<T> enumType, String name) { ... }
}
La caratteristica sorprendente della dichiarazione “ Enum<E estende Enum<E>> ” è il fatto che la classe appena definita Enum e il relativo parametro di tipo appena definito E appaiono nel limite dello stesso parametro di tipo. Vuol dire che il Enum il tipo deve essere istanziato per uno dei suoi sottotipi. Per capire perché questo ha senso, considera che ogni tipo enum è tradotto in un sottotipo di Enum .
Ecco il tipo enum inventato Colore :
enum Colore {ROSSO, BLU, VERDE}
Il compilatore lo traduce nella seguente classe:
lezione finale pubblica Colore si estende Enum<Colore> {
public static final Color[] valori() { return (Color[])$VALUES.clone(); }
public static Color valueOf(Nome stringa) { ... } private Color(String s, int i) { super(s, i); }finale pubblico statico Colore ROSSO;
finale pubblico statico Colore BLU;
finale pubblico statico Colore VERDE;colore finale statico privato $VALUES[];
statico {
ROSSO = nuovo Colore("ROSSO", 0);
BLU = nuovo Colore("BLU", 1);
VERDE = nuovo Colore("VERDE", 2);
$VALORI = (nuovo Colore[] {ROSSO, BLU, VERDE});
}
}
L’eredità ha l’effetto che il Colore type eredita tutti i metodi implementati in Enum<Colore> . Tra questi c’è il confrontare con metodo. IL Colore. confrontare con il metodo dovrebbe probabilmente richiedere a Colore come argomento. Per far sì che ciò accada, lezione Enum è generico e il Enum.compareTo il metodo richiede Enum il parametro di tipo E come argomento. Di conseguenza, digita Colore derivato da Enum<Colore> eredita a confrontare con metodo che richiede a Colore come argomento, esattamente come dovrebbe.
Se analizziamo la dichiarazione “ Enum<E estende Enum<E>> “possiamo vedere che questo modello ha diversi aspetti.
Innanzitutto, c’è il fatto che il parametro di tipo associato è il tipo stesso: “ Enum <E si estende Enum <E>> “. Si assicura che solo i sottotipi di tipo Enum sono consentiti come argomenti di tipo. (In teoria, digitare Enum potrebbe essere istanziato su se stesso, come in Enum<Enum> , ma questo non è certamente voluto ed è difficile immaginare una situazione in cui tale esemplificazione possa essere utile.)
In secondo luogo, esiste il fatto che il tipo associato al parametro è il tipo con parametri Enum <E> , che utilizza il parametro type E come argomento di tipo del limite. Questa dichiarazione garantisce che la relazione di ereditarietà tra un sottotipo e un’istanza di Enum è della forma X estende Enum<X> “. Un sottotipo come “ X estende Enum<Y> ” non può essere dichiarato a causa dell'argomento tipo ’ Y non sarebbe entro i limiti; solo sottotipi di Enum<X> sono entro i limiti.
In terzo luogo, c’è il fatto che Enum è innanzitutto generico. Significa che alcuni dei metodi di class Enum accettare un argomento o restituire un valore di tipo sconosciuto (o altrimenti dipendere da un tipo sconosciuto). Come già sappiamo, questo tipo sconosciuto diventerà in seguito un sottotipo X Di Enum<X> . Quindi, nel tipo parametrizzato Enum<X> , questi metodi coinvolgono il sottotipo X e vengono ereditati nel sottotipo X . IL confrontare con il metodo è un esempio di tale metodo; viene ereditato dalla superclasse in ciascuna sottoclasse e in ogni caso ha una firma specifica della sottoclasse.
In sintesi, la dichiarazione” Enum<E estende Enum<E>> ” può essere decifrato come: Enum è un tipo generico che può essere istanziato solo per i suoi sottotipi e tali sottotipi erediteranno alcuni metodi utili, alcuni dei quali accettano argomenti specifici del sottotipo (o altrimenti dipendono dal sottotipo).
Parametri di tipo limite inferiore delle classi
I parametri di tipo possono avere diversi limiti, come in classe Box<T estende Number> {...} . Ma un parametro di tipo non può avere un limite inferiore, ovvero un costrutto come scatola di classe<T Grande Numero> {...} non è consentito. Perchè no? La risposta è: è inutile perché non ti comprerebbe nulla, se fosse permesso. Vediamo perché i parametri di tipo del limite inferiore delle classi creano confusione esplorando cosa significa un limite superiore su un parametro di tipo.
Il limite superiore di un parametro di tipo ha tre effetti:
- Istanziazione limitata. Il limite superiore limita l’insieme di tipi che possono essere utilizzati per la creazione di istanze del tipo generico. Se dichiariamo a classe Box<T estende Number> {...} quindi il compilatore garantirebbe che solo i sottotipi di Numero può essere utilizzato come argomento di tipo. Cioè, a Scatola<Numero> o a Scatola<Lungo> è consentito, ma a Scatola<Oggetto> O Scatola<String> verrebbe rifiutato
Esempio (di istanziazione limitata a causa di un limite superiore su un parametro di tipo):
classe Box<T estende Numero> {
valore T privato;
public Box(T t) { valore = t; }
...
} prova di classe {
public static void main(String[] args) {
Box<Long> boxOfLong = nuovo Box<Long>(0L); // Bene
Box<String> boxOfString = nuovo Box<String>(""); // errore: la stringa non rientra nei limiti
}
}
- Accesso ai membri non statici. Il limite superiore dà accesso a tutti i metodi e campi pubblici non statici del limite superiore. Nella realizzazione del ns classe Box<T estende Number> {...} possiamo invocare tutti i metodi pubblici non statici definiti nella classe Numero , ad esempio intValue() ad esempio. Senza il limite superiore il compilatore rifiuterebbe qualsiasi invocazione di questo tipo. Esempio (di accesso a membri non statici a causa di un limite superiore su un parametro di tipo):
classe Box<T estende Numero> {
valore T privato;
public Box(T t) { valore = t; }
public int incremento() { return valore.intValue()+1; } // <= sarebbe un errore senza il limite del numero
...
}
- Digitare Cancellazione. Il limite superiore più a sinistra viene utilizzato per la cancellazione del tipo e sostituisce il parametro type nel codice byte. Nel nostro classe Box<T estende Number> {...} tutte le occorrenze di T verrebbe sostituito dal limite superiore Numero . Ad esempio, se class Scatola ha un campo privato di tipo T e un metodo insieme vuoto (contenuto T) per impostare questo campo privato, il campo sarebbe di tipo Numero dopo la cancellazione del tipo e il metodo verrebbe tradotto in un metodo insieme vuoto (contenuto del numero) .
Esempio (dell'utilizzo del limite superiore di un parametro del tipo nella cancellazione del tipo – Prima tipo cancellazione):
classe Box< T estende il numero > {
privato T valore;
casella pubblica( T t) { valore = t; }
...
}
- Esempio (dell'utilizzo del limite superiore di un parametro del tipo nella cancellazione del tipo –
Dopo
- tipo cancellazione):
classe Scatola {
privato Numero valore;
casella pubblica( Numero t) { valore = t; }
...
}
- Inoltre, il limite superiore più a sinistra appare in ulteriori posizioni, ad esempio nei calchi inseriti automaticamente e nei metodi bridge.
Se fossero consentiti limiti inferiori sui parametri di tipo, quali effetti collaterali avrebbero o dovrebbero avere? Se un costrutto come scatola di classe<T Grande Numero> {...} fossero consentiti, cosa significherebbe? Cosa significherebbero i 3 effetti collaterali di un limite di parametro di tipo superiore – istanziazione limitata, accesso a membro non statico, cancellazione di tipo – per un limite inferiore?
Istanziazioni limitate. Il compilatore potrebbe limitare l'insieme di tipi utilizzabili per la creazione di istanze del tipo generico con un parametro di tipo con limite inferiore. Ad esempio, il compilatore podrème consente le istanziazioni come Scatola<Numero> E Scatola<Oggetto> da a Scatola<T Grande Numero> e rifiutare istanze come Scatola<Lungo> O Scatola<Corto> . Questo sarebe un effetto in linea con l’effetto collaterale restrittivo descritto per i limiti dei parametri di tipo superiore.
Accesso ai membri non statici. Un limite inferiore del parametro di tipo non dà accesso a nessun metodo particolare oltre a quelli ereditati dalla classe Oggetto . Nell’esempio di Scatola<T Grande Numero> i supertipi di Numero non hanno nulla in comune, tranne il fatto che sono tipi di riferimento e quindi sottotipi di Oggetto . Il compilatore non può presupporre che il campo di tipo T è di tipo Numero o un suo sottotipo. Invece, il campo di tipo T può essere di qualsiasi supertipo di Numero , ad esempio Serializzabile O Oggetto . L’invocazione di un metodo come intValue() non è più indipendente dai tipi e il compilatore dovrebbe rifiutarlo. Di conseguenza, il limite inferiore del parametro di tipo non consentirebbe l’accesso a membri non statici oltre quelli definiti nella classe Oggetto e quindi ha lo stesso effetto di “no bound”.
Digitare Cancellazione . Seguendo questa linea logica, non ha senso sostituire le occorrenze del parametro type con il suo limite inferiore più a sinistra. Dichiarare un metodo come il costruttore Scatola( T T) come costruttore Scatola( Numero T) non ha senso, considerando questo T è sostituito da un supertipo di Numero . UN Oggetto podrède può essere giustamente passato al costruttore in un’istanza Scatola<Oggetto> e il costruttore lo rifiuterebbe. Questo sarebbe completamente sbagliato. Pertanto, la cancellazione del tipo sostituirebbe tutte le occorrenze della variabile type T per tipo Oggetto e non dal suo limite inferiore. Ancora una volta, il limite inferiore avrebbe lo stesso effetto di "no limit".
Vuoi capire cosa significherebbe se entrambi si abbassassero? _E_ erano consentiti limiti superiori? Personalmente non voglio nemmeno pensarci e preferirei schedarlo sotto la voce “non gestibile”, se permettete.
La conclusione è: tutto ciò a “ Grande ” Il limite ti comprerebbe è la restrizione che solo i supertipi di Numero possono essere utilizzati come argomenti di tipo. E anche questo viene spesso frainteso. NON significherebbe questo classe Scatola<T super Numero> {...} contiene solo istanze di supertipi di Numero . Al contrario, come demonstra l’esempio seguente!
Esempio (dell'utilizzo del limite superiore di un parametro del tipo nella cancellazione del tipo – Prima tipo cancellazione):
classe Box< Supernumero T > {
privato T valore;
casella pubblica( T t) { valore = t; }
...
}
Esempio (dell'utilizzo del limite superiore di un parametro del tipo nella cancellazione del tipo – Dopo tipo cancellazione):
classe Scatola {
privato Oggetto valore;
casella pubblica( Oggetto t) { valore = t; }
...
}
UN scatola di classe<T Grande Numero> {...} verrebbe tradotto per tipo cancellazione in a Scatola contenente un Oggetto campo e il suo costruttore verrebbe tradotto in Scatola(Oggetto t) . Questo è fondamentalmente diverso da a scatola di classe<T si estende Numero> {...} , che verrebbe tradotto in a Scatola contenente a Numero campo e il suo costruttore verrebbe tradotto in Casella(Numero t) . Di conseguenza, a Scatola<Numero> istanziato da a classe Box<T estende Number> {...} sarebbe diverso da a Scatola<Numero> istanziazione da a classe Scatola<T super Numero> {...} , il che potrège creare confusione. Per questo motivo i limiti inferiori non hanno senso sui parametri di tipo delle classi.
Parametri di tipo limite inferiore dei metodi
Insieme ai metodi e ai tipi di argomenti, può occasionalmente essere utile un parametro di tipo con un limite inferiore.
Esempio (di un metodo che trarrebbe vantaggio da un parametro di tipo con un limite inferiore):
classe Coppia<X,Y> {
prima il privato X;
secondo Y privato;
...
pubblico < Una super X , BsuperY > B addToMap(Map<A,B> map) { // errore: il parametro di tipo non può avere un limite inferiore
return map.put(primo, secondo);
}
}
prova di classe {
public static void main(String[] args) {
Coppia<String,Long> coppia = nuova Coppia<>("ABC",42L);
Mappa<CharSequence, Numero> mappa = HashMap<CharSequence, Numero>();
Numero numero =pair.addToMap(mappa);
}
}
IL aggiungi alla mappa() Il metodo aggiunge il contenuto della coppia a una mappa. Qualsiasi mappa che può contenere supertipi di X E Y farebbe. Quella della mappa Mettere() Il metodo restituisce il valore trovato nella mappa per la chiave specificata, se esiste già una voce di valore-chiave per la chiave nella mappa. Il valore restituito della mappa Mettere() il metodo verrà restituito da aggiungi alla mappa() metodo. In queste circostanze si vorrebbe dichiarare il metodo come mostrato sopra: la mappa è parametrizzata con i supertipi dei parametri di tipo della coppia e il aggiungi alla mappa() Il tipo restituito del metodo è il tipo di valore della mappa.
Poiché il compilatore non acconsente ai limiti inferiori sui parametri di tipo, è necessaria una soluzione alternativa.
An alternative solution che mi viene mente è l’uso di un carattere jolly, perché i caratteri jolly possono avere un limite inferiore. Ecco una soluzione alternativa utilizzando un carattere jolly.
Esempio (di una soluzione alternativa all’esempio precedente utilizzando i caratteri jolly):
classe Coppia<X,Y> {
prima il privato X;
secondo Y privato;
...
pubblico Oggetto addToMap(Map< ? SuperX , ? superY mappa >) {
return map.put(primo, secondo);
}
}
prova di classe {
public static void main(String[] args) {
Coppia<String,Long> coppia = nuova Coppia<>("ABC",42L);
Mappa<CharSequence, Numero> mappa = HashMap<CharSequence, Numero>();
Numero numero = (Numero) coppia.addToMap(mappa);
}
}
Funziona, tranne per il fatto che non c’è modo di dichiarare il tipo restituito come desiderato. Sarebbe il supertipo di Y che il compilatore acquisisce dal tipo di mappa, ma non esiste una sintassi per specicarlo. Non dichiariamo il tipo restituito a “ ? superY “, Perché “ ? superY ” è un carattere jolly e non un tipo e pertenato non è consentito come tipo restituito. Non abbiamo scelta e dobbiamo utilizarlo Oggetto invece come tipo restituito del nostro metodo. Questo tipo di restituzione piuttosto non specifico a sua volta forza i chiamanti di aggiungi alla mappa() metodo per convertire il valore restituito da Oggetto al suo tipo reale. Questo non è esattamente quello che avevamo in mente.
Un’altra soluzione alternativa è lOZYX1Uso di metodi statici. Ecco una soluzione alternativa con un metodo statico anziché non statico.
Esempio (di una soluzione alternativa per l’esempio precedente utilizzando un metodo statico):
classe Coppia<X,Y> {
prima il privato X;
secondo Y privato;
...
pubblico statico < UN , B ,X estende A ,Y estende B > B addToMap(Coppia<X,coppia Y>, Mappa< UN , B mappa >) {
return map.put(coppia.primo,coppia.secondo);
}
}
prova di classe {
public static void main(String[] args) {
Coppia<String,Long> coppia = nuova Coppia<>("ABC",42L);
Mappa<CharSequence, Numero> mappa = HashMap<CharSequence, Numero>();
Numero numero = Paio .addToMap( paio ,mappa);
}
}
Il generico aggiungi alla mappa() Il metodo ha quattro parametri di tipo: due segnaposto X E Y per il tipo della coppia e due segnaposto UN E B per il tipo di mappa. UN E B sono supertipi di X E Y , Perché X E Y sono dichiarati con UN E B come limiti superiori. (Si noti che i parametri di tipo del metodo generico X E Y non avere nulla a che fare con il Paio classe X E Y parametri. I nomi X E Y vengono riutilizzati per il metodo generico per renderli facilente riconoscibili come parametri di tipo della coppia. Utilizzando quattro parametri di tipo possiamo dichiarare il tipo restituito esattamente come desiderato: è lo stesso tipo del tipo valore della mappa.
Esempio (di uso illegale del parametro di tipo nel contesto statico di una classe generica):
classe SomeClass <T> {
// inizializzatore statico, campo statico, metodo statico
statico {
AlcuniClass< T Test > = nuovo SomeClass< T >(); // errore
}
privato statico T informazioni globali; // errore
pubblico statico T getGlobalInfo() { // errore
restituire informazioni globali;
}
// inizializzatore non statico, campo non statico, metodo non statico
{
AlcuniClass< T Test > = nuovo SomeClass< T >();
}
privato T informazionilocali;
pubblico T getLocalInfo() {
restituire informazionilocali;
}
// tipi nidificati statici
pubblico statico classe Il fallimento estende l'eccezione {
finale privata T informazioni; // errore
fallimento pubblico ( T t) { informazioni = t; } // errore
pubblico T getInfo() { restituisce informazioni; } // errore
}
privato interfaccia Copiabile {
T copia(); // errore
}
privato enum Stato {
VALIDO, NON VALIDO;
privato T informazioni; // errore
public void setInfo( T t) { informazioni = t; } // errore
pubblico T getInfo() { restituisce informazioni; } // errore
}
// tipi nidificati non statici
pubblico classe Accessore {
pubblico T getInfo() { return localInfo; }
}
}
L’esempio illustra che il parametro tipo non può essere utilizzato nel contesto statico di una classe generica. Mostra inoltre che le interfacce nidificate e i tipi enum sono considerati membri di tipo statico della classe. Solo le classi interne, ovvero le classi nidificate non statiche, possono utilizzare il parametro tipo della classe generica che le racchiude.
L’ambito del parametro di tipo di un’interface e l’intera definizione dell'’interface, ad eccezione di eventuali campi o tipi annidati. Questo perché i campi e i tipi annidati definiti in un’interface sono implicitamente statici.
Esempio (di uso illegale del parametro di tipo in un’interface generica):
interfaccia SomeInterface <T> {
// campo
AlcuniClass< T Valore > = nuova SomeClass< T >(); // errore
// tipo nidificato
classe Accessore {
pubblico T getInfo() { // errore
restituire valore.getGlobalInfo();
}
}
// metodi
T getValore();
}
L’esempio mostra che i campi di un’interface sono implicitamente statici, quindi il parametro type non può essere utilizzato in nessun punto della dichirazione di un campo di un’interface generica. Allo stesso modo, la classe nidificata è considerata una classe nidificata statica, non una classe interna, e per questo motivo lOZYX3Uso del parametro type in qualsiasi punto della classe nidificata è illegale.
Soluzione alternativa per classi e interfacce statiche nidificate:
In caso di classi e interfacce nidificate statiche questa non è una grave calamità. Come soluzione alternativa possiamo generare la classe statica o l’interfaccia stessa.
Esempio (soluzione alternativa: generare il tipo statico nidificato):
involucro di classe <T> {
finale privata T l'Oggetto;
Wrapper pubblico( T t) { l'Oggetto = t; }
pubblico T getWrapper() { return l'Oggetto; } pubblico immutabile <T> makeImmutable() {
restituisce nuovo immutabile <T> (l'Oggetto);
}
pubblico mutevole <T> makeMutable() {
restituisce un nuovo Mutable <T> (l'Oggetto);
}
privato statico <A> A makeClone(A theObject) { ... }classe finale statica pubblica Immutabile <A> { // ora è una classe generica
finale privata UN l'Oggetto;public Immutabile( UN argomento) {
theObject = makeClone(arg);
}
pubblico UN getWrappedItem() {
return makeClone(l'Oggetto);
}
}
classe statica pubblica Mutable <A> {
... simile ...
}
}
Non esiste una soluzione alternativa per i tipi enum nidificati perché non possono essere generici.
Soluzione alternativa per le classi statiche nidificate:
Se il tipo statico annidato è una classe, una soluzione alternativa potrebbe trasformare la classe statica in una classe interna non statica.
Esempio (soluzione alternativa: utilizzare una classe interna):
involucro di classe <T> {
finale privata T l'Oggetto;
Wrapper pubblico( T t) { l'Oggetto = t; }
pubblico T getWrapper() { return l'Oggetto; } public Mutable makeMutable() {
ritorno Questo. nuovo Mutable(l'Oggetto);
}
public Immutabile makeImmutable() {
ritorno Questo. nuovo Immutabile(l'Oggetto);
}
privato statico <A> A makeClone(A theObject) { ... }classe finale pubblica Immutabile { // non è più una classe statica
finale privata T l'Oggetto;public Immutabile( T argomento) {
theObject = makeClone(arg);
}
pubblico T getWrappedItem() {
return makeClone(l'Oggetto);
}
}
classe pubblica Mutabile
... simile ...
}
}
Questa soluzione alternativa comporta un certo overraccarico perché tutte le classi interne hanno un rifference hidden a un’istanza del tipo esterno, di cui in questo esempio non hanno né bisogno né trarre vantaggio; il riferimento nascosto è solo il bagaglio. Spesso le classi interne vengono combinate con le interfacce per mantenere la classe interna un dettaglio di implementazione privata della classe che la cchiude. Possiamo fare lo stesso qui.
Esempio (la soluzione alternativa precedente perfetta):
involucro di classe <T> {
finale privata T l'Oggetto;
Wrapper pubblico( T t) { l'Oggetto = t; }
pubblico T getWrapper() { return l'Oggetto; } pubblico mutevole <T> makeMutable() {
restituisce this.new Mutable(theObject);
}
pubblico immutabile <T> makeImmutable() {
restituisce this.new Immutable(theObject);
}
privato statico <A> A makeClone(A theObject) { ... }interfaccia pubblica Immutabile<S> {
S getWrappedItem();
}
interfaccia pubblica Mutevole<S> {
S getWrappedItem();
void setWrappedItem(S arg);
}
privato classe finale ImmutableImplementation implementa Immutable<T> {privato finale T theObject;
public ImmutableImplementation(T arg) {
theObject = makeClone(arg);
}
public T getWrappedItem() {
return makeClone(l'Oggetto);
}
}
privato classe MutableImplementation implementa Mutable<T> {
... simile ...
}
}
Come puoi vedere, le interfacce pubbliche nidificate devono essere generiche mentre le classi interne private possono utilizzare il parametro di tipo della classe che le racchiude.

Lascia una risposta