I generici Java spiegati in breve con esempi

Per ulteriori informazioni, consultare queste domande frequenti:

http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html#SECTION1

Ecco in breve alcuni punti importanti di queste FAQ.

Esempio (di tipo generico):

class Pair<X,Y>  { 
  private X first;
  private Y second;   public Pair(X a1, Y a2) {
    first  = a1;
    second = a2;
  }
  public X getFirst()  { return first; }
  public Y getSecond() { return second; }
  public void setFirst(X arg)  { first = arg; }
  public void setSecond(Y arg) { second = 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 is replaced by  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 come argomento e restituiscono il tipo di metodi di istanza e i tipi di campi di istanza.
I parametri di tipo possono essere dichiarati con limiti. I limiti danno accesso ai metodi del tipo sconosciuto rappresentato dal parametro type. Nel nostro esempio non invochiamo alcun metodo di tipo sconosciuto X E Y . Per questo motivo i 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 in fase di runtime.  Solleva 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> .  Questi sono i cosiddetti downcast, 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 .  This expectation stems from the fact that such a type relationship exists for arrays:  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 covariante.)

La mancanza di una relazione di super-sottotipo tra istanziazioni dello stesso tipo generico ha varie conseguenze.  Ecco un esempio.

Esempio:

void stampaTutto(ArrayList<Object> c) {
  for (Object 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 richiede 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 è 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); 
  Pair<Integer,Integer> pair = intPairArr[1];
  Integer i = pair.getFirst();
  pair.setSecond(i);
}
addElementi vuoti statici( Oggetto[] objArr) {
  objArr[0] = new Pair<Integer,Integer>(0,0);
  objArr[1] = new Pair<String,String>("","");      // should fail with 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 riesce a 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 contengono 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 di 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 in 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 indipendentemente dall’argomento tipo.  Il tipo con parametri jolly Elenco<? estende il numero> denotes the family of all list types where the element type is a subtype of  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 nonché i costruttori possono avere parametri di tipo. La sintassi per la dichiarazione dei parametri di tipo formale è simile alla sintassi per i 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 all’elenco dei parametri di tipo 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 {
  public static <A estende il Paragonabile<A>> A max(Collezione< UN > xs) {
    Iterator< UN > xi = xs.iterator();
    UN w = xi.successivo();
    while (xi.hasNext()) {
      UN x = xi.successivo();
      if (w.compareTo(x) < 0) w = x;
    }
    return 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>{
  private static class Entry<K,V> {
     K key;
     V value;
     Entry<K,V> left = null;
     Entry<K,V> right = null;
     Entry<K,V> parent;
  }
  private transient Entry<Key,Data> root = null;
  ...
  private Entry<Key,Data> getEntry(Key key) {
     Entry<Key,Data> p = root;
     Key k = key;
     while (p != null) {
       int cmp = k. confrontaCon(p.tasto) ; 
       if (cmp == 0)
         return p;
       else if (cmp < 0)
         p = p.left;
       else 
         p = p.right; 
     }
     return null;
  }
  public boolean containsKey(Key key) {
     return getEntry(key) != null;
  }
  ...
}

Nell’esempio precedente, il parametro type 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’implementazione del ns Mappa ad albero classe.
  • Solo i tipi “entro i limiti” possono essere utilizzati per creare un’istanza del tipo generico.   In the example, a parameterized type such as 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 di array possono essere utilizzati come parametri di tipo associati.

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 primitivi come int e tipi di array come Oggetto[] non sono consentiti come parametro di tipo associato.

Tipi di classi, come 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
    Number n = t.remove(0);
  }
}
prova di classe {
  public static void main(String[] args) {
     X<ArrayList< Lungo >>   x1 = new 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 di un altro parametro di tipo.

Esempio (di un parametro di tipo utilizzato come parametro di tipo associato):

classe Tripla <T> {
  private T fst, snd, trd;
  public < 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 parametrizzato di quella classe.

Ulteriori opportunità per l’utilizzo dei parametri di tipo come limiti di altri parametri di tipo includono situazioni in cui un tipo annidato è definito all’interno di un tipo generico 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 {     // static members
    public enum EnumType {THIS, THAT}
    public static Object staticField;
    public static void staticMethod() { ... }

    // non-static members
    public class InnerClass { ... }
    public Object nonStaticField;
    public void nonStaticMethod() { ... }

    // constructors
    public SuperClass() { ... }

    // private members
    private Object privateField; 

    ...
}

la classe finale pubblica SomeClass<T si estende SuperClasse > {
    private T object;
    public SomeClass(T t) { object = t; } 

    public String toString() {
        return
         "static nested type    : "+T.EnumType.class+"\n" 
        +"static field          : "+T.staticField+"\n"
        +"static method         : "+T.staticMethod()+"\n"
        +"non-static nested type: "+T.InnerClass.class+"\n"
        +"non-static field      : "+object.nonStaticField+"\n"
        +"non-static method     : "+object.nonStaticMethod()+"\n"
        +"constructor           : "+(new T())+"\n"                    // error
        +"private member        : "+object.privateField+"\n"          // error 
        ;
    }
}

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 {
  private final String name;
  public  final String name() { ... }   private final int ordinal;
  public  final int ordinal() { ... }

  protected Enum(String name, int ordinal) { ... }

  public String           toString() { ... }
  public final boolean    equals(Object other) { ... }
  public final int        hashCode() { ... }
  protected final Object  clone() throws CloneNotSupportedException { ... }
  public final int        compareTo( E o) { ... }

  public final Class< E > getDeclaringClass() { ... }
  public static <T extends 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[] values() { return (Color[])$VALUES.clone(); }
  public static Color valueOf(String name) { ... }   private Color(String s, int i) { super(s, i); }

  public static final Color RED;
  public static final Color BLUE;
  public static final Color GREEN;

  private static final Color $VALUES[];

  static {
    RED = new Color("RED", 0);
    BLUE = new Color("BLUE", 1);
    GREEN = new Color("GREEN", 2);
    $VALUES = (new Color[] { RED, BLUE, GREEN });
  }
}

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 type 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 type 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 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:

    1. 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):
class Box<T extends Number> { 
   private T value; 
   public Box(T t) { value = t; } 
   ...
}  prova di classe { 
   public static void main(String[] args) { 
       Box<Long>    boxOfLong   = new Box<Long>(0L);   // Bene
       Box<String>  boxOfString = new Box<String>(""); // errore: la stringa non rientra nei limiti
   }
}

  1. 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):

class Box<T extends Number> { 
   private T value; 
   public Box(T t) { value = t; } 
   public int increment() { return value.intValue()+1; } // <= sarebbe un errore senza il limite del numero
   ...
} 

    1. 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 (di utilizzo del limite superiore su un parametro di tipo nella cancellazione del tipo – Prima tipo cancellazione):
classe Box< T estende il numero > { 
   privato T valore; 
   public Box( T t) { valore = t; } 
   ...
} 

      Esempio (di utilizzo del limite superiore su un parametro di tipo nella cancellazione del tipo –

Dopo

      tipo cancellazione):

 

classe Scatola { 
   privato Numero valore; 
   public Box( 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 membri non statici, cancellazione di tipo – per un limite inferiore?

Istanziazioni limitate. Il compilatore potrebbe limitare l’insieme di tipi che possono essere utilizzati per la creazione di istanze del tipo generico con un parametro di tipo con limite inferiore.  Ad esempio, il compilatore potrebbe consentire istanziazioni come Scatola<Numero> E Scatola<Oggetto> da a Scatola<T Grande Numero> e rifiutare istanze come Scatola<Lungo> O Scatola<Corto> .  Questo sarebbe 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 potrebbe 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 “nessun limite”.

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 dimostra l’esempio seguente!

Esempio (di utilizzo del limite superiore su un parametro di tipo nella cancellazione del tipo – Prima tipo cancellazione):

classe Box< Supernumero T > {
   privato T valore;
   public Box( T t) { valore = t; }
   ...
}

Esempio (di utilizzo del limite superiore su un parametro di tipo nella cancellazione del tipo – Dopo tipo cancellazione):

classe Scatola {
   privato Oggetto valore;
   public Box( 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 potrebbe 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> {
   private X first;
   private Y second;
   ...
   public < Una super X , BsuperY > B addToMap(Map<A,B> map) { // errore: il parametro di tipo non può avere un limite inferiore
      return map.put(first, second);
   }
}
prova di classe { 
   public static void main(String[] args) { 
     Pair<String,Long> pair = new Pair<>("ABC",42L);
     Map<CharSequence, Number> map = HashMap<CharSequence, Number>();
     Number number = pair.addToMap(map);
   }
}

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 consente limiti inferiori sui parametri di tipo, è necessaria una soluzione alternativa.

Una soluzione alternativa che mi viene in 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> {
   private X first;
   private Y second;
   ...
   pubblico Oggetto addToMap(Map< ? SuperX , ? superY mappa >) { 
      return map.put(first, second);
   }
}
prova di classe { 
   public static void main(String[] args) { 
     Pair<String,Long> pair = new Pair<>("ABC",42L);
     Map<CharSequence, Number> map = HashMap<CharSequence, Number>();
     Number number = (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 specificarlo.  Non dobbiamo dichiarare il tipo restituito a “ ? superY “, Perché “ ? superY ” è un carattere jolly e non un tipo e pertanto non è consentito come tipo restituito. Non abbiamo scelta e dobbiamo utilizzarlo 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 è l’uso 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> {
   private X first;
   private Y second;
   ...
   pubblico statico < UN , B ,X estende A ,Y estende B > B addToMap(Coppia<X,coppia Y>, Mappa< UN , B mappa >) {
      return map.put(pair.first,pair.second);
   }
}
prova di classe { 
   public static void main(String[] args) { 
     Pair<String,Long> pair = new Pair<>("ABC",42L);
     Map<CharSequence, Number> map = HashMap<CharSequence, Number>();
     Number number = 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   are declared with 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 facilmente riconoscibili come parametri di tipo della coppia. Utilizzando quattro parametri di tipo possiamo dichiarare il tipo restituito preciso 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 {
    SomeClass< T Test > = nuovo SomeClass< T >(); // errore
  }
  privato statico T globalInfo;          // errore
  pubblico  statico T getGlobalInfo() {        // errore
    return globalInfo;
  }
  // inizializzatore non statico, campo non statico, metodo non statico
  { 
    SomeClass< T Test > = nuovo SomeClass< T >();
  }
  privato        T informazionilocali;
  pubblico         T getLocalInfo() { 
    return localInfo;
  }
  // tipi nidificati statici
  pubblico statico classe Il fallimento estende l'eccezione {
    private final T info;           // errore
    public Failure( T t) { informazioni = t; }      // errore
    pubblico T getInfo() { return info; }    // errore
   }
  privato interfaccia Copiabile {
    T copy();                       // errore
  }
  privato enum Stato {
    VALID, INVALID;
    privato T informazioni; // errore
    public void setInfo( T t) { informazioni = t; } // errore
    pubblico T getInfo() { return info; }    // errore
  }
  // tipi nidificati non statici
  pubblico classe Accessore {
    pubblico T getInfo() { return localInfo; }
  }
}

L’esempio illustra che il parametro type 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 type della classe generica che le racchiude.

L’ambito del parametro di tipo di un’interfaccia è l’intera definizione dell’interfaccia, ad eccezione di eventuali campi o tipi annidati.  Questo perché i campi e i tipi annidati definiti in un’interfaccia sono implicitamente statici.

Esempio (di uso illegale del parametro di tipo in un’interfaccia generica):

interfaccia SomeInterface <T> {
  // campo
  SomeClass< T Valore > = nuova SomeClass< T >();  // errore
  // tipo nidificato
  classe Accessore {
    pubblico T getInfo() {            // errore
      return value.getGlobalInfo();
    }
  }
  // metodi
  T getValore();
}

L’esempio mostra che i campi di un’interfaccia sono implicitamente statici, quindi il parametro type non può essere utilizzato in nessun punto della dichiarazione di un campo di un’interfaccia generica.  Allo stesso modo, la classe nidificata è considerata una classe nidificata statica, non una classe interna, e per questo motivo l’uso 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> {
  private final T l'Oggetto;

  public Wrapper( T t) { l'Oggetto = t; }
  pubblico T getWrapper() { return l'Oggetto; }   public Immutable <T> makeImmutable() {
        return new Immutable <T> (l'Oggetto);
  }
  public Mutable <T> makeMutable() {
        return new Mutable <T> (l'Oggetto);
  }
  private static <A> A makeClone(A theObject) { ... }

  public static final class Immutable <A> { // ora è una classe generica
        private final UN l'Oggetto;

        public Immutable( UN argomento) {
            theObject = makeClone(arg);
        }
        pubblico UN getWrappedItem() {
            return makeClone(theObject);
        }
  }
  public static class Mutable <A> {
        ... similar ...
  }
}

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> {
  private final T l'Oggetto;

  public Wrapper( T t) { l'Oggetto = t; }
  pubblico T getWrapper() { return l'Oggetto; }   public Mutable makeMutable() {
      return Questo. nuovo Mutable(l'Oggetto);
  }
  public Immutable makeImmutable() {
      return Questo. nuovo Immutabile(l'Oggetto);
  }
  private static <A> A makeClone(A theObject) { ... }

  public final class Immutable {  // non è più una classe statica
      private final T l'Oggetto;

      public Immutable( T argomento) {
          theObject = makeClone(arg);
      }
      pubblico T getWrappedItem() {
          return makeClone(theObject);
      }
  }
  public class Mutable 
        ... similar ...
  }
}

Questa soluzione alternativa comporta un certo sovraccarico perché tutte le classi interne hanno un riferimento nascosto a un’istanza del tipo esterno, di cui in questo esempio non hanno né bisogno né traggono 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 privato della classe che la racchiude.  Possiamo fare lo stesso qui.

Esempio (la soluzione alternativa precedente perfezionata):

involucro di classe <T> {
  private final T l'Oggetto;

  public Wrapper( T t) { l'Oggetto = t; }
  pubblico T getWrapper() { return l'Oggetto; }   public Mutable <T> makeMutable() {
      return this.new Mutable(theObject);
  }
  public Immutable <T> makeImmutable() {
      return this.new Immutable(theObject);
  }
  private static <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> { 

      private final T theObject;

      public ImmutableImplementation(T arg) {
          theObject = makeClone(arg);
      }
      public T getWrappedItem() {
          return makeClone(theObject);
      }
  }
  privato classe MutableImplementation implementa Mutable<T> { 
        ... similar ...
  }
}

Come puoi vedere, le interfacce pubbliche nidificate devono essere generiche mentre le classi private interne possono utilizzare il parametro di tipo della classe che le racchiude.

 

 

 

 

 

Lascia una risposta

Scopri di più dal blog di Velislav Krastev

Iscriviti ora per continuare a leggere e avere accesso all'archivio completo.

Continua a leggere