lunedì 16 gennaio 2012

Introduzione a java e generics parte 2 - Subtyping & wildcard


Leggi l'articolo aggiornato sul mio sito

In Java un tipo A è un sottotipo di un altro tipo B se questi sono legati da una clausola extends o implements
A extends B o A implements B
(Integer è sottotipo di Number, List<E> è sottotipo di Collection<E>)
 
Subtyping è riflessiva e transitiva:
 
//Se A è sottotipo di B allora B è supertipo di A//
 
Ogni tipo reference è sottotipo di Object ed Object è supertipo di ogni reference type 

Principio di sostituzione

interface Collection<E>{
...
public boolean add(E elem);
...
}
Principio di sostituzione: possiamo aggiungere Integer e Double a collezioni di Numbers (Integer e Double sono sottotipi di Number)
List<Number> numeri = new
 ArrayList<Number>();
numeri.add(2);
 
numeri.add(3.14);
assert
   numeri.toString().equals(
    "[2, 3.14]");
  • OK List<Number> è sottotipo di Collection<Number>
  • OK 2 ha tipo* Integer che è sottotipo di Number
  • OK 3.14 ha tipo* Double che è sottotipo di Number
  • Per ora tutto OK
Eccoci al dunque …

Wildcard con extends

interface Collection<E>{
...
public boolean addAll(
  Collection<? extends E> c);
...
}
  • OK, posso inserire in una collezione di tipo E elementi di tipo E
  • ? extends E → OK, posso inserire in una collezione di tipo E elementi appartenenti ad una collezione di qualsiasi sottotipo di E
List<Number> numeri = new ArrayList<Number>();
List<Integer> interi = Arrays.asList(1,2);
List<Double> doubles = Arrays.asList(2.78,3.14);
numeri.addAll(interi);
numeri.addAll(doubles)
  • OK Integer e Double sono sottotipi di Number
  • List<Integer> è sottotipo di List<? extends Number> (idem per Double)
List<Integer> interi = Arrays.asList(1,2);
List<? extends Number> numeri = interi;
numeri.ADD(3.14); // → Errore di compilazione
assert interi.toString().equals(
   "[1, 2, 3.14]");
List<? extends Number> può essere una lista di qualsiasi tipo di numero! Non è detto che sia una lista di Double! :-(

wildcard con super

public static <T> void copia(List<? super T> dest, List<? extends T> src){
 for (int i=0; i < src.size(); i++) {
 
   dest.set(i,src.get(i)); // ← Attenzione
 }
}
? super T → lista destinazione di supertipo di T
List<Object> oggetti = Arrays.<Object>asList(2,3.14,"four");
List<Integer> interi = Arrays.asList(5,6);
Collections.copia(oggetti, interi);
assert oggetti.toString().equals("[5, 6, four]");

Get and Put Principle

  • GET: Utilizzare il wildcard extends quando si deve solamente recuperare valori da una struttura
  • PUT: Utilizzare il wildcard super quando si deve solamente inserire dati in una struttura
  • GET and PUT: Non utilizzare i wildcard quando si deve sia prendere che inserire

Eccezioni al Get/Put principle

  • ? extends E → è possibile inserire null
  • ? super T → è possibile prelevare Object

Sporco trucco di programmazione

public void rebox(
   Box<? extends Object> box) {
    reboxHelper(box);
}
private static <V> void reboxHelper(Box<V> box) {
    box.put(box.get());
}
Viene effettuata una chiamata ad un “helper” senza wildcard

Array

Il subtyping degli array in java è covariante: S sottotipo di T → S[] sottotipo di T[] :!: Il codice seguente viene compilato
Integer[] interi = 
   new Integer[] {1,2,3};
Number[] numeri = interi;
numeri[2] = 3.14; // --> oops
ma attenzione che a runtime viene lanciato un errore
Exception in thread "main"
java.lang.ArrayStoreException: java.lang.Double
// numeri--> 1 2 3.14
Passiamo adesso ai generics
List<Integer>  interi= Arrays.asList(1,2,3);
List<Number> numeri = interi; → non compila!
numeri.add(3.14);
il codice non compila il Subtyping per i generics è controvariante: S supertipo di T → List<S> è sottotipo di List <? super T>

Covariante

   
   S        ----- extends -----> T
   S[]      ----- extends -----> T[]

   String   ----- extends -----> Object
   String[] ----- extends -----> Object[]

Controvariante

     S                ----- extends -----> T
     List<T>          ----- extends -----> List <? super S>

     String           ----- extends -----> Object
     List<Object>     ----- extends -----> List<? super String>

Cattura del wildcard

Quando viene invocato un metodo generico il parametro di tipo deve essere scelto in modo da “combaciare” con il tipo rappresentato dal wildcard → wildcard capture
public static  <T>  void reverse(List<T> list)
public static void reverse(List<?> list)
<?> è un sinonimo di <? extends Object>
public static  <T>  void reverse(List<T> list){
List<T> tmp = new ArrayList<T>(list);
for (int i = 0 ; i < list.size() ; i++){
list.set(i, tmp.get(list.size() -i - 1) );
}
}
il seguente codice non compila
public static void reverse(List<?> list){
List<Object> tmp = new ArrayList<Object>(list);
for (int i = 0 ; i < list.size() ; i++){
list.set(i, tmp.get(list.size() -i - 1) );
}
}
errore di compilazione
The method set(int, capture#3-of ?) in the type List<capture#3-of ?> is not applicable for the arguments (int, Object)

Restrizioni del wildcard

  • Creazione dell'istanza
  • Chiamata a metodo generico
  • Supertipo

Creazione dell'istanza

List<?> l = new ArrayList<?>();
   NO! Errore di compilazione
 
List<? super Number >  s = new ArrayList<Number>(); 
  OK

Chiamata a metodo generico

public class Lista {
public static <T> List<T>factory() { return new ArrayList<T>(); }
} .. OK
List<?> l = Lista.factory(); OK
List<?> l2 = Lista.<Object>factory(); OK
List<?> l3 = Lista.<?>factory(); → ERRORE
List<?> l4 = Lista.<List<?>>factory(); OK

Supertipo

Class Lista extends ArrayList<?> {} → NO
Class Lista2 implements List<?> {} → NO
Class List3  implements  ArrayList<List<?>> {} → OK

Nessun commento:

Posta un commento