Generics Java

Da Wikipedia, l'enciclopedia libera.

1leftarrow.pngVoce principale: Java 5.

Il JDK 1.5 ha introdotto alcune estensioni al linguaggio Java. Una di questa è l'introduzione dei generics o tipi generici. Un generics è uno strumento che permette la definizione di un tipo parametrizzato, che viene esplicitato successivamente in fase di compilazione secondo le necessità; i generics permettono di definire delle astrazioni sui tipi di dati definiti nel linguaggio.


Vi sono svariati vantaggi nell'uso dei generics:

  • Fornisce una migliore gestione del type checking durante la compilazione;
  • Evita il casting da Object. I.e.;
  • Invece di utilizzare:
String title=((String) words.get(i)).toUppercase();

verrà utilizzato:

String title=words.get(i).toUppercase();


Vi sono però anche degli svantaggi:

  • Si definisce:
List<String>words=new ArrayList<String>();

invece di:

List words=new ArrayList<String>();


L'esempio più comune del loro utilizzo è nella definizione/uso dei cosiddetti contenitori. Prima dell'uscita del JDK 1.5 per poter gestire in modo trasparente tipi di dati differenti si doveva ricorrere al fatto che in Java ogni classe deriva in modo implicito dalla classe Object. Per esempio se si doveva implementare una lista concatenata il codice era il seguente:

List myIntList = new LinkedList();
myIntList.add(new Integer(0));

e invece per recuperare l'elemento appena inserito si doveva scrivere

Integer x = (Integer) myIntList.iterator().next();

Si noti il cast a Integer necessario poiché myIntList in realtà lavora su oggetti Object. Dall'introduzione del JDK 1.5 invece è possibile utilizzare un codice come il seguente:

List<Integer> myIntList = new LinkedList<Integer>();
myIntList.add(new Integer(0));

dove viene esplicitamente espresso che la lista myIntList lavorerà solo su oggetti di tipo Integer. Per recuperare l'elemento appena inserito il codice è il seguente:

Integer x = myIntList.iterator().next();

Si noti che ora il cast non è più necessario visto che la lista è di interi.

Implementazione[modifica | modifica sorgente]

Java 5 non ha esteso il linguaggio bytecode per implementare i generics. Questo vuol dire che i generics sono in realtà solo dei costrutti sintattici, emulati a livello di bytecode tramite il solito meccanismo della classe Object (descritto sopra).[1] Dichiarare

List<Integer> myIntList = new LinkedList<Integer>();

equivale a livello di codice a dichiarare

List myIntList = new LinkedList(); // Lista di Object

e ad eseguire implicitamente le conversioni Object->Integer e Integer->Object per leggere e scrivere gli elementi.

I generics hanno quindi eliminato i problemi riguardanti la tipizzazione; adesso gli elementi della lista devono essere Integer e non (per esempio) String e tale controllo è eseguito a tempo di compilazione.

Erasure[modifica | modifica sorgente]

L'Erasure è il processo che converte il programma codificato con i generici nella forma senza di essi, che rispecchia più da vicino il bytecode prodotto. Questo termine non è del tutto corretto in quanto vengono si rimossi i generici, ma vengono anche aggiunti i cast. L'aggiunta di questi cast non è esplicita e il linguaggio di progetto fornisce la Cast-iron guarantee: ossia il cast implicito aggiunto alla compilazione dei generici: non può mai fallire. Questa è una regola che si applica per il codice che non presenta unchecked warnings. I vantaggi dell'implementazione via Erasure, sono:

  • mantenere le cose semplici senza aggiunta di dettagli o altro;
  • mantenere le cose piccole ad esempio con una sola implementazione di List;
  • semplificare l'evoluzione, la stessa libreria può essere accessibile da un codice generico e da codici legacy.

Se un elemento Y deriva da un elemento X, non si può dire che una collezione di elementi di Y derivi dalla collezione di elementi X, perché in generale ciò risulta un'operazione impossibile; solo alcune di questo potrebbero essere anche sicuri, soprattutto per ciò che riguarda gli array e la lettura, ma ciò non è vero in generale.

Consideriamo la classe generica LinkedList <T>: prendiamo due sue istanziazioni: LinkedList <Number> e LinkedList<Integer>. Sono due tipi differenti, incompatibili fra loro, anche se Integer estende Number; situazione in contrasto a quella che si verifica negli array, dove Integer[] è un sottotipo di Number[]. Per verificarlo, creiamo due liste:

 LinkedList<Number> l1 = new LinkedList<Number>();
 LinkedList<Integer> l2 = new LinkedList<Integer>();

E consideriamo i due possibili assegnamenti l1 = l2 e l2 = l1; si ottiene in ogni caso errore perché LinkedList<Integer> non estende LinkedList<Number>.

Gli assegnamenti in precedenza sono impossibili perché viene violato il principio di sostituzione: Ad una variabile di un certo tipo può essere assegnato un valore di ogni sottotipo; un metodo con un argomento di un certo tipo può essere chiamato con un argomento di ogni sottotipo.

List<Number> numbers = new ArrayList<Number>();
numbers.add(2);
numbers.add(3.14d);
assert numbers.toString().equals("[2, 3.14]");

Qui il principio vale fra List e ArrayList e fra Number e Integer Double in maniera rispettiva. List<Integer> invece non è un sottotipo di List<Number> in quanto viene violato nuovamente il principio di sostituzione, ad esempio:

List<Integer> integers = Arrays.asList(1, 2);
List<Number> numbers = integers; // non compila
numbers.add(3.14d);
assert integers.toString().equals("[1, 2,3.14]");

Tipi parametrici varianti (Wildcard)[modifica | modifica sorgente]

Non può esistere una compatibilità generale fra i tipi parametrici. Se si è alla ricerca della compatibilità, bisogna prendere in considerazione casi specifici e tipi di parametri di singoli metodi. Quindi, alla normale notazione dei tipi generici List<T>, usata per creare oggetti, si affianca una nuova notazione, pensata per esprimere i tipi accettabili come parametri in singoli metodi.

Si parla quindi di tipi parametrici varianti, in Java detti wildcard.

Posto che la notazione List<T> denota il normale tipo generico, si introducono le seguenti notazioni wildcard:

  • tipo covariante List<extends T>: cattura le proprietà dei List<X> in cui X estende T; si usa per specificare tipi che possono essere solo letti.
  • tipo controvariante List<super T>: cattura le proprietà dei List<X> in cui X è esteso da T; si usa per specificare tipi che possono essere solo scritti.
  • tipo bivariante List<?>: cattura tutti i List<T> senza distinzioni; si usa per specificare i tipi che non consentono né letture né scritture.

Definizione di una classe generica[modifica | modifica sorgente]

Ecco un esempio di classe generics

public class Gen<X,Y> {
 
  private final X var1;
  private final Y var2;
 
  public Gen(X x,Y y) {  
    var1 = x;
    var2 = y;   
  }
 
  public X getVar1() {
    return var1;
  }
 
  public Y getVar2() {
    return var2;
  }
 
  public String toString() { 
    return "(" + var1 + ", " + var2 + ")";  
  }
}

Questa classe non serve a nulla da sola, deve essere quindi utilizzata un'altra struttura, come nell'esempio successivo:

Gen<String, String> esempio1 = new Gen<String, String>("esempio", "uno");
Gen<String, Integer> esempio2 = new Gen<String, Integer>("esempio", 2);
 
System.out.println("primo esempio: " + esempio1);
System.out.println("secondo esempio: " + esempio2);

Note[modifica | modifica sorgente]

  1. ^ Generics in C#, Java, and C
informatica Portale Informatica: accedi alle voci di Wikipedia che trattano di informatica