Programmazione Parametrica ( a.k.a. Generics )
Introduzione ai meccanismi e concetti della programmazione parametrica Generics e relationi di sottotipo wildcards generics e vincoli Implemendaizone di classi e metodi parametrici Supporto per i generics nella JVM
Programmazione polimorfa Polimorfo ~ multiforme, di molti tipi Programmazione polimorfa: creazione di costrutti (classi e metodi) che possono essere utilizzati in modo uniforme su dati di tipo diverso In Java, tradizionalmente ottenuta mediante i meccanismi di sottotipo ed ereditarietà Da Java 1.5. anche mediante i meccanismi di parametrizzazione di tipo (a.k.a. generics)
Variabili di Tipo Le variabili (o parametri) di tipo pemettono di creare astrazioni di tipo Classico caso di utilzzo nelle classi Container E = variabile di tipo astrae (e rappresenta) il tipo delle componenti public class ArrayList { public ArrayList() {... } public void add(E element) {... }... } Continua
Variabili di Tipo Possono essere istanziate con tipi classe o interfaccia Vincolo: tipi che istanziano variabili di tipo non possono essere primitivi (devono essere tipi riferimento) Classi wrapper utili allo scopo ArrayList ArrayList ArrayList // No! ArrayList
Variabili di tipo e controlli di tipo Utilizzare variabili di tipo nella programmazione permette maggiori controlli sulla correttezza dei tipi in fase di compilazione Aumenta quindi la solidità e robustezza del codice Continua
Variabili di tipo e controlli di tipo Un classico caso di utilizzo di containers Il cast è problematico, per vari motivi verboso, fonte di errori a run time Ma necessario per la compilazione e per localizzare leventuale errore a run time Continua List intList = new LinkedList(); intList.add(new Integer(0)); Integer x = (Integer) intList.iterator().next();
Variabili di tipo e controlli di tipo Container generici: più sintetici ed eleganti Compilatore può stabilire un invariante sugli elementi della lista garantire lassenza di errori a run-time in forza di quellinvariante. List intList = new LinkedList (); intList.add(new Integer(0)); Integer x = intList.iterator().next(); Continua
Variabili di tipo e controlli di tipo Ora non è possibile aggiungere una stringa ad intlist:List Le variabili di tipo rendono il codice parametrico più robusto e semplice da leggere e manutenere List intList = new LinkedList (); intList.add(new Integer(0)); Integer x = intList.iterator().next();
Classi parametriche: definizione // nulla di particolare, a parte i parametri // tra parentesi angolate public interface List { void add(E x); Iterator iterator(); } public interface Iterator { F next(); boolean hasNext(); } Un frammento delle interfacce List e Iterator nel package java.util.*
Classi parametriche: uso Quando utilizziamo un tipo parametrico, tutte le occorrenze dei parametri formali sono rimpiazzate dallargomento (parametro attuale) Meccanismo simile a quello del passaggio dei parametri in un metodo Diversi usi generano tipi diversi Ma... classi parametriche compilate una sola volta danno luogo ad un unico file.class
Sintassi: uso GenericClassName Esempio: ArrayList HashMap Scopo: Fornire tipo specifici per ciascuna delle variabili di tipo introdotte nella dichiarazione
public class Pair { public Pair(T firstElement, S secondElement) { first = firstElement; second = secondElement; } public T getFirst() { return first; } public S getSecond() { return second; } private T first; private S second; } Esempio: Pair Una semplice classe parametrica per rappresentare coppie di oggetti Continua
Esempio: Pair Una semplice classe parametrica per rappresentare coppie di oggetti: I metodi getFirst e getSecond restituiscono il primo e secondo elemento, con i tipi corrispondenti Pair result = new Pair ("Harry Hacker", harrysChecking); String name = result.getFirst(); BankAccount account = result.getSecond();
Variabili di tipo: convenzioni VariabileSignificato Inteso ETipo degli elementi in una collezione KTipo delle chiavi in una mappa VTipo dei valori in una mappa T,S,UTipi generici
Esempio: LinkedList public class LinkedList {... public E removeFirst() { if (first == null) throw new NoSuchElementException(); E element = first.data; first = first.next; return element; }... private Node first; private class Node { E data; Node next; } } Continua
Esempio: LinkedList Notiamo la struttura della classe ausiliaria che specifica la struttura dei nodi Se la classe è interna, come in questo caso, non serve alcun accorgimento allinterno di Node possiamo utilizzare il tipo E, il cui scope è tutta la classe Se invece la classe è esterna, dobbiamo renderla generica public class ListNode
Esempio: LinkedList private class Node { F data; Node next; } public class LinkedList {... public E removeFirst() { if (first == null) throw new NoSuchElementException(); E element = first.data; first = first.next; return element; }... private Node first; } Continua
File LinkedList.java 001: import java.util.NoSuchElementException; 002: 003: /** 004: A linked list is a sequence of nodes with efficient 005: element insertion and removal. This class 006: contains a subset of the methods of the standard 007: java.util.LinkedList class. 008: */ 009: public class LinkedList 010: { 011: /** 012: Constructs an empty linked list. 013: */ 014: public LinkedList() 015: { 016: first = null; 017: } 018: Continua
File LinkedList.java 019: /** 020: Returns the first element in the linked list. the first element in the linked list 022: */ 023: public E getFirst() 024: { 025: if (first == null) 026: throw new NoSuchElementException(); 027: return first.data; 028: } 029: 030: /** 031: Removes the first element in the linked list. the removed element 033: */ 034: public E removeFirst() 035: { Continua
File LinkedList.java 036: if (first == null) 037: throw new NoSuchElementException(); 038: E element = first.data; 039: first = first.next; 040: return element; 041: } 042: 043: /** 044: Adds an element to the front of the linked list. element the element to add 046: */ 047: public void addFirst(E element) 048: { 049: Node newNode = new Node(element,first); 052: first = newNode; 053: } Continua
File LinkedList.java 054: 055: /** 056: Returns an iterator for iterating through this list. 057: 058: */ 059: public ListIterator listIterator() 060: { 061: return new LinkedListIterator(); 062: } 063: 064: private Node first; 065: 066: private class Node 067: { 068: E data; 069: Node next; 070: } 071: Continua
LinkedListIterator La definiamo come classe interna di LinkedList Implements linterfaccia ListIterator Ha accesso al campo first e alla classe interna Node
Classe LinkedListIterator 072: private class LinkedListIterator implements ListIterator 073: { 074: /** 075: Costruisce un iteratore posizionato sul primo 076: elemento della lista 077: */ 078: public LinkedListIterator() 079: { 080: last = null; // ultimo nodo visitato 081: previous = null; // precedente a position 082: } 083: Continua
LinkedListIterator – next() 084: /** 085: Posizione literatore sul prossimo. il prossimo elemento 087: */ 088: public E next() 089: { 090: if (!hasNext()) 091: throw new NoSuchElementException(); 092: previous = last; // Remember for remove 093: 094: if (last == null) 095: last = first; 096: else 097: last = last.next; 098: 099: return last.data; 100: } 101: 102: Continua
LinkedListIterator – hasNext() 102: /** 103: Testa se cè un elemento dopo lultimo 104: visitato. true se esiste un elemento dopo 106: lultimo visitato 107: */ 108: public boolean hasNext() 109: { 110: if (last == null) 111: return first != null; 112: else 113: return last.next != null; 114: } 115: 116: Continua
LinkedListIterator – add() 117: /** Aggiunge un elemento dopo last e sposta 118: literatore sul prossimo elemento. 119: */ 121: public void add(E element) 122: { 123: if (last == null) 124: { 125: addFirst(element); last = first; 127: } 128: else 129: { 130: Node newNode = new Node(); 131: newNode.data = element; 132: newNode.next = last.next;// (1) 133: last.next = newNode; // (2) 134: last = newNode; // (3) 135: } 136: previous = last; // (4) 137: } Continua
LinkedListIterator – add() last
LinkedListIterator – remove() 140: /** Rimuove lelemento puntato da last. Può essere 141: invocato solo dopo una chiamata a next() 142: */ 143: public void remove() 144: { 145: if (previous == last) 146: throw new IllegalStateException(); 147: 148: if (last == first) 149: { 150: removeFirst(); 151: } 152: else 153: { 154: previous.next = last.next; // (1) 155: } 156: last = previous; // (2) 157: } Continua
LinkedListIterator –remove() last
Classe LinkedListIterator 159: /** 160: Sets the last traversed element to a different 161: value. element the element to set 163: */ 164: public void set(E element) 165: { 166: if (position == null) 167: throw new NoSuchElementException(); 168: position.data = element; 169: } 170: 171: private Node position; 172: private Node previous; 173: } // end LinkedListIterator 174: } // end LinkedList
File ListIterator.java 01: /** 02: A list iterator allows access of a position in a linked 03: list. This interface contains a subset of the methods 04: of the standard java.util.ListIterator interface. The 05: methods for backward traversal are not included. 06: */ 07: public interface ListIterator 08: { 09: /** 10: Moves the iterator past the next element. the traversed element 12: */ 13: E next(); 14: 15: /** 16: Tests if there is an element after the iterator 17: position. Continua
File ListIterator.java true if there is an element after the iterator 19: position 20: */ 21: boolean hasNext(); 22: 23: /** 24: Adds an element before the iterator position 25: and moves the iterator past the inserted element. element the element to add 27: */ 28: void add(E element); 29: 30: /** 31: Removes the last traversed element. This method may 32: only be called after a call to the next() method. 33: */ Continua
File ListIterator.java 34: void remove(); 35: 36: /** 37: Sets the last traversed element to a different 38: value. element the element to set 40: */ 41: void set(E element); 42: }
Generics e sottotipi Consideriamo La prima istruzione è sicuramente legale, la seconda è più delicata … Number è una classe astratta che ha Integer, Double e altre classi wrapper come sottotipi Per capire se la seconda istruzione sia da accettare come legale continuiamo con lesempio … Continua List li = new ArrayList (); List ln = li;
Generics e sottotipi Come si vede abbiamo un problema nella terza istruzione inseriamo un Double (via auto- boxing, ricordate?) nella quarta estraiamo un Integer ! Il vero problema è nella seconda istruzione soluzione: errore di compilazione per lassegnamento al secondo Continua List li = new ArrayList (); List ln = li; ln.add(3.14); Integer i = li.get(0); // uh oh...
Generics e sottotipi In generale, dati due tipi A e B, ed tipo generico C abbiamo che: Quindi, per le stesse ragioni di prima Continua A B NON implica C C Set NON è sottotipo di Set
Generics e sottotipi Le limitazione sulle relazioni di sottotipo sono contro-intuitive uno degli aspetti più complessi dei generics Non solo … sono spesso anche troppo restrittive illustriamo con un esempio Continua
Generics e sottotipi Un metodo che stampa gli elementi di una collezione Versione tradizionale Continua void printCollection(Collection c) { Iterator i = c.iterator(); for (k = 0; k < c.size(); k++) { System.out.println(i.next()); }
Generics e sottotipi Stampa degli elementi di una collezione Versione generics: primo tentativo utile solo per Collection non per qualsiasi collezione Collection non è il supertipo di tutte le collezioni Continua void printCollection(Collection c) { for (Object e:c) System.out.println(e); }
Wildcards Stampa degli elementi di una collezione Versione generics: secondo tentativo Collection è il supertipo di tutte le Collections la wildcard ? indica un qualche tipo, non specificato Continua void printCollection(Collection c) { for (Object e:c) System.out.println(e); }
Wildcards Possiamo estrarre gli elementi di c al tipo Object Corretto perché, qualunque sia il loro vero tipo, sicuramente è sottotipo di Object Continua void printCollection(Collection c) { for (Object e:c) System.out.println(e); }
Wildcards Continua Collection c = new ArrayList (); c.add(new String()); // errore di compilazione! Daltra parte … visto che non sappiamo esattamente quale tipo indica ?, non possiamo assegnare valori
Date un esempio di codice che causerebbe errore in esecuzione se permettessimo di aggiungere elementi a Collection Domanda
Lultima istruzione invocherebbe intValue() sul primo elemento di ci ma quellelemento ha tipo String … In realtà il compilatore anticipa lerrore, segnalando il problema sulla add() Risposta Collection ci = new ArrayList ; Colletion c = ci; c.add(a string); // non compila ci.get(0).intValue();
Wilcards con vincoli ( bounded ) Shapes : un esempio classico public abstract class Shape { public abstract void draw(Graphics g); } public class Circle extends Shape { private int x, y, radius; public void draw(Graphics g) {... } } public class Rectangle extends Shape { private int x, y, width, height; public void draw(Graphics g) {... } } Continua
Wilcards con vincoli ( bounded ) Graphics e il metodo draw() Solito problema: drawAll() non può essere invocato su una List public class Graphics { // disegna una shape public void draw(Shape s) { s.draw(this); } // disegna tutte le shapes di una lista public void drawAll(List shapes) { for (Shape s:shapes) s.draw(this) }... } Continua
Bounded Wilcards Quello che ci serve è un metodo che accetti liste di qualunque (sotto) tipo di Shape List bounded wildcard indica un tipo sconosciuto, sottotipo di Shape il bound può essere qualunque tipo riferimento (classe o interfaccia) Ora il metodo ha la flessibilità necessaria e desiderata void drawAll(List shapes) {... } Continua
Bounded Wilcards Graphics e il metodo draw() public class Graphics { // disegna una shape public void draw(Shape s) { s.draw(this); } // disegna tutte le shapes di una lista public void drawAll(List shapes) { for (Shape s:shapes) s.draw(this) }... } Continua
Bounded Wilcards Daltra parte, cè sempre un prezzo da pagare Il solito vincolo … Non possiamo modificare strutture con questi tipi [ perché? ] void addRectangle(List shapes) { // errore di compilazione shapes.add(new Rectangle()); }
Metodi che dipendono da una variabile di tipo Possono essere definiti allinterno di qualunque classe, generica o meno N.B. Evitiamo List perché List renderebbe il metodo poco utile (non utilizzabie su liste arbitrarie) /* trasforma un array in una lista, copiando * tutti gli elementi di a in l */ static void array2List(Object[] a, List l){... } Metodi Generici Continua
Metodi Generici Al solito però non possiamo aggiungere elementi ad una struttura (o modificare) con elementi di tipo wildcard /* trasforma un array in una lista, copiando * tutti gli elementi di a in l */ static void array2List(Object[] a, List l) { for (Object o : a) l.add(o) // compiler error } Continua
Metodi Generici Soluzione: rendiamo il metodo parametrico possiamo invocare questo metodo con una qualunque lista il cui tipo sia supertipo del tipo base dellarray purché sia un tipo riferimento /* trasforma un array in una lista, copiando * tutti gli elementi di a in l */ static void array2List(T[] a, List l) { for (T o : a) l.add(o) }
Invocazione di metodi generici Nellinvocazione di un metodo generico non è necessario passare largomento di tipo il compilatore inferisce il tipo, se esiste, dai tipi degli argomenti del metodo
Invocazione di metodi generici Continua Object[] oa = new Object[100]; Collection co = new ArrayList (); fromArrayToCollection(oa, co); // T = Object (inferito) String[] sa = new String[100]; Collection cs = new ArrayList (); fromArrayToCollection(sa, cs); // T = String (inferito) fromArrayToCollection(sa, co); // T = Object (inferito) Integer[] ia = new Integer[100]; Float[] fa = new Float[100]; Number[] na = new Number[100]; Collection cn = new ArrayList (); fromArrayToCollection(ia, cn); // T = Number (inferito) fromArrayToCollection(fa, cn); // T = Number (inferito) fromArrayToCollection(na, cn); // T = Number (inferito) fromArrayToCollection(na, co); // T = Object (inferito) fromArrayToCollection(na, cs); // compiler error
Wildarcds vs variabili di tipo Ci sono situazioni in cui è possibili usare equivalentemente wildcards e variabili di tipo. Nella libreria Collection troviamo Continua interface Collection { public boolean containsAll(Collection c); public boolean addAll(Collection c);... }
Wildarcds vs variabili di tipo Queste specifiche possono essere espresse equivalentemente con metodi parametrici Il secondo metodo è parametrico in qualunque sottotipo di E i bounds si possono utilizzare anche con variabili, non solo con wildcards Continua interface Collection { public boolean containsAll(Collection c); public boolean addAll(Collection c);... }
Wildcards e variabili di tipo possono coesistere Notiamo la dipendenza tra i tipi dei due parametri: il tipo della sorgente deve essere un sottotipo del tipo della destinazione Wildarcds vs variabili di tipo Continua interface Collection { public static void copy(List dest, List src)... }
Potremmo analogamente riformulare in modo da evitare le wildcards Come scegliere tra le due soluzioni? Wildarcds vs variabili di tipo Continua interface Collection { public static void copy( dest, List src)... }
In generale, preferiamo le wildcards quando entrambe le soluzioni sono possibili Possiamo darci la seguente rule of thumb se una variabile di tipo ha una unica occorrenza nella specifica di un metodo e il tipo non è il target di un operazione di modifica utilizziamo una wildcard al posto della variabile Wildarcds vs variabili di tipo
Variabili di Tipo e Bounds Abbiamo visto che possiamo definire bounds anche per variabili di tipo (non solo wildcards) Un caso paradigmatico public static > T max(Collection coll) { T candidate = coll.iterator().next(); for (T e : coll) if candidate.compareTo(e) < 0) candidate = e; return candidate; }
Variabili di Tipo e Bounds Il bound su una variabile impone vincoli sulla variabile, determinando quali metodi possono essere utilizzati su valori del tipo variabile Qui il bound è ricorsivo: informa che i valori con cui operiamo forniscono un metodo compareTo() che gli argomenti del metodo devono essere dello stesso tipo dei valori public static > T max(T max(List coll)
Generics e erasure I tipi generici sono significativi a compile-time La JVM opera invece con tipi raw Il tipo raw è ottenuto da un tipo generico mediante un processo detto erasure che rimuove le variabili di tipo il bycode generato da un tipo generico è lo stesso che viene generato dal corrispondente tipo raw.
Generics e erasure Generano lo stesso bytecode List words = new ArrayList (); words.add(hi); words.add(there); String welcome = words.get(0) + words.get(1); List words = new ArrayList(); words.add(hi); words.add(there); String welcome = (String)words.get(0) + (String)words.get(1);
Generics e erasure Cast-iron guarantee i cast impliciti che vengono aggiunti dalla compilazione di codice generico non falliscono mai.