Cominciamo
Generics e Collections vanno a braccetto con altre nuove feature (introdotte da Java5):
- boxing e unboxing;
- nuovo ciclo for;
- funzioni che accettano un numero variabile di argomenti.
La loro combinazione è sinergica: l'intero è maggiore della somma delle parti!
Versione "cool"
List<Integer> ints = Arrays.asList(1,2,3); int s = 0; for (int n : ints) { s += n; } assert s == 6;
Probabilmente il codice è comprensibile anche senza alcuna spiegazione …
List<Integer> ints = Arrays.asList(1,2,3);
- List ed ArrayList sono parte del CollectionFramework. List è generico: è possibile scrivere List<E> per indicare liste contenenti elementi di tipo E. List<Integer> indica liste contenenti elementi di tipo Integer.
- Boxing ed unboxing automagicamente applicati.
- Il metodo statico Arrays.asList prende un numero variabile di argomenti.
for (int n : ints) { s += n; }
- Ciclo foreach: permette di associare ad una variabile i valori contenuti in un oggetto iterabile. Ad ogni iterazione si prende l'elemento successivo
assert s == 6;
- Lo statement assert permette di controllare che l'asserzione fatta sia corretta …
- Asserzione vera
- AssertionError
- … disabilitate di default …
Versione old fashion
List ints = Arrays.asList(new Integer [] { new Integer(1), new Integer(2), new Integer(3) } ); int s = 0 ; for (Iterator it = ints.iterator() ; it.hasNext() ; ){ int n = ((Integer)it.next()).intValue(); s += n; } assert(s == 6);
- Il precedente codice è meno leggibile del primo
- Senza i generics non è possibile indicare che tipo di elementi vogliamo inserire nella lista sono necessari dei cast
- Senza boxing ed unboxing è necessario eseguire manualmente la conversione esempio: .intValue()
- Senza funzioni ad argomenti variabili è necessario passare alla asList un array “preimpostato”
- Senza il ciclo foreach è necessario istanziare un iteratore per scandire la lista
- .. Si c'è anche un altro modo …
Generics: Base
Una classe o interfaccia può dichiarare di ricevere uno o più parametri di tipo:
- Sono scritti tra parentesi angolate (<T>)
- I tipi attuali devono essere forniti
- quando si dichiara una variabile
- quando si istanzia un oggetto
List <String> words = new ArrayList<String>(); words.add("Hello "); words.add("world !"); String s = words.get(0) + words.get(1); assert s.equals("Hello world !");
- La classe ArrayList<E> implementa l'interfaccia List<E>
- words è una lista contenente stringhe
- Vengono inserite due stringhe e successivamente vengono recuperate
- … tutto senza cast!
Codice senza l'ausilio dei generics …
List words = new ArrayList(); words.ADD("Hello "); words.ADD("world !"); String s = (String)words.get(0) + (String)words.get(1); assert s.equals("Hello world !");
Type erasure
Il bytecode compilato dei due precedenti esempi è (grossomodo) identico → retro compatibilità. I generics sono implementati tramite la type erasure
compile time | run time |
---|---|
List<Integer> | List |
List<String> | List |
List<List<String» | List |
… | … |
I generics eseguono implicitamente il cast che deve essere esplicitato nella versione senza generics
Cast-iron guarantee: I cast impliciti aggiunti dalla compilazione dei generics non falliscono mai!(*) (*) si applica esclusivamente al caso in cui non vengano inviati dal compilatore dei “unchecked warnings”
Type erasure, come mai?
- Semplicità → il bytecode è identico
- Dimensioni → c'è solo una classe List
- Evoluzione → il bytecode (*) è retro compatibile e le librerie con e senza generics possono coesistere
- E' possibile evolvere il proprio codice “con calma e con pazienza”
- (*) Solo se ricompilato per versioni vecchie di java
Generics VS C++ Template
Java | C++ |
---|---|
List<List<String» | List< List<String> > (con lo spazio!) |
Type erasure: una sola versione della classe | Expansion: n versioni della solita classe; una per ogni tipo definito a compile-time → code bloat (?ottimizzazioni?) |
Tipi reference
- Classi
- Istanze
- Array (tutti)
- Possono assumere il valore null
- Sono tutti fgli di Object
Tipi primitivi
8 tipi primitivi hanno un corrispondente “tipo reference” nel package java.lang
Primitivo | Reference |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
bool | Boolean |
char | Character |
Boxing e unboxing
Boxing → conversione da primitivo a reference
Unboxing → conversione da reference a primitivo
La conversione viene fatta in automatico
int e → new Integer(e) // boxing Integer e → e.intValue() // unboxing
Esempi
// OK public static int somma(List<Integer> ints){ int s = 0; for ( int n :ints) { s += n ; } return s; } //Troppe conversioni! Performance :-( public static Integer sommaInteger(List<Integer> ints) { Integer s = 0; for ( int n :ints) { s += n ; } return s; }
Binary Numeric Promotion
Se uno degli operandi è un reference type viene applicato l'unboxing. Poi
- Se uno degli operandi è double anche l'altro viene promosso a double
- Se uno degli operandi è foat anche l'altro viene promosso a foat
- Se uno degli operandi è long anche l'altro viene promosso a long
- Altrimenti entrambi vengono promossi a int
Si applica a vari operatori binari ( tra cui == )
Pericolo boxing/uboxing
Per i primitivi == signifca “uguaglianza dei valori”
Per i reference == signifca “stessa identità”
L ist<Integer> bigs = Arrays.asList(100,200,300); assert sommaInteger(bigs) == somma(bigs); assert sommaInteger(bigs) != sommaInteger(bigs); // non raccomandato
I generics funzionano solo con i reference
Interi da -128 a 127, caratteri da 0 a \u007f, Byte e Boolean possono essere cachati
List<Integer> smalls = Arrays.asList(1,2,3); assert sommaInteger(smalls) == somma(smalls);// 6 assert sommaInteger(smalls) == sommaInteger(smalls); // 6, non raccomandato
Posso assegnare null ad un primitivo? → NO
Due paroline sul ciclo foreach
For (Pippo p: pippi) …
- Applicabile a istanze di java.lang.Iterable<T>
- Applicabile ad array[]
- Esegue in automatico eventuali boxing ed unboxing
int[] ints = {1,2,3,4}; for (Integer i :ints) System.out.println(i);
Metodi generici
class Lists { public static <T> List<T> toList(T[] arr){ List<T> list = new ArrayList<T>(); for (T elem: arr) list.add(elem); return list; } ...
- Il metodo toList accetta un array di tipo T[] e ritorna un List<T> per ogni tipo T
- Si deve indicare <T> all'inizio della frma del metodo statico
- T è un parametro di tipo
- Ogni metodo che dichiara un parametro di tipo è un metodo generico
List<Integer> ints = Lists.toList( new Integer[] {1,2,3}); List<String> strings = Lists.toList( new String[] {"ciao","mondo"});
Boxing e unboxing gratuiti!
Varargs
Che noia inserire gli elementi nell'array!
public static <T> List<T> toList(T ... arr){ List<T> list = new ArrayList<T>(); for (T elem: arr) list.add(elem); return list; } ... List<Integer> ints = Lists.toList( 1,2,2); List<String> strings = Lists.toList( "ciao","mondo");
Abbiamo sostituito
- T[] con T …
- L'array[] con valori separati da virgola
Qualsiasi numero di argomenti può precedere il vararg, niente deve seguire il vararg
Attenzione! Il tipo T non viene sempre dedotto dal compilatore, a volte è necessario esplicitarlo
Lists.<Object>toList( 1,"mondo");
Non è detto che Integer e String abbiano in comune solo Object! (Serializable, Comprarable, …)
Asserzioni
Possono essere abilitate tramite i fag della JVM -ea o -enableassertions Altrimenti stanno a dormire …
Nessun commento:
Posta un commento