1.31k likes | 1.33k Views
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.
E N D
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. 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<E> { 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<BankAccount>ArrayList<Measurable> ArrayList<double> // No! ArrayList<Double>
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 l’eventuale errore a run time List intList = new LinkedList(); intList.add(new Integer(0)); Integer x = (Integer) intList.iterator().next(); Continua
Variabili di tipo e controlli di tipo • Container generici: più sintetici ed eleganti • Compilatore può • stabilire un invariante sugli elementi della lista • garantire l’assenza di errori a run-time in forza di quell’invariante. List<Integer> intList = new LinkedList<Integer>(); 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<Integer> • Le variabili di tipo rendono il codice parametrico più robusto e semplice da leggere e manutenere List<Integer> intList = new LinkedList<Integer>(); intList.add(new Integer(0)); Integer x = intList.iterator().next();
Classi parametriche: definizione • Un frammento delle interfacce List e Iterator nel package java.util.* // nulla di particolare, a parte i parametri // tra parentesi angolate public interface List<E> { void add(E x); Iterator<E> iterator(); } public interface Iterator<E> { E next(); boolean hasNext(); }
Classi parametriche: uso • Quando utilizziamo un tipo parametrico, tutte le occorrenze dei parametri formali sono rimpiazzate dall’argomento (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<Type1, Type2, . . .> Esempio: ArrayList<BankAccount> HashMap<String, Integer> Scopo: Fornire tipo specifici per ciascuna delle variabili di tipo introdotte nella dichiarazione
Esempio: Pair<T,S> • Una semplice classe parametrica per rappresentare coppie di oggetti public class Pair<T, S>{ 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;} Continua
Esempio: Pair<T,S> • 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<String, BankAccount> result = new Pair<String, BankAccount> ("Harry Hacker", harrysChecking); String name = result.getFirst();BankAccount account = result.getSecond();
Esempio: LinkedList<E> public class LinkedList<E>{ . . . 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<E> private class Node<F>{ F data; Node next;}public class LinkedList<E>{ . . . public E removeFirst() { if (first == null) throw new NoSuchElementException(); E element = first.data; first = first.next; return element; } . . . private Node<E> first; } Continua
Esempio: LinkedList<E> • Notiamo la struttura della classe ausiliaria che specifica la struttura dei nodi • Se la classe è interna, come in questo caso, non serve alcun accorgimento • all’interno di Node possiamo utilizzare il tipo E, il cui scope è tutta la classe • Se invece la classe è esterna, dobbiamo renderla generica public classListNode<E>
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<E> 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. 021: @return 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. 032: @return 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. 045: @param element the element to add 046: */ 047:public void addFirst(E element) 048: { 049: Node newNode = new Node(); 050: newNode.data = element; 051: newNode.next = 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<E> 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<E> • Implements l’interfaccia ListIterator<E> • Ha accesso al campo first e alla classe interna Node
Classe LinkedListIterator 072:private class LinkedListIterator implements ListIterator<E> 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 l’iteratore sul prossimo. 086: @return 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 l’ultimo 104: visitato. 105: @return true se esiste un elemento dopo 106: l’ultimo 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: l’iteratore 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
last LinkedListIterator–add()
LinkedListIterator–remove() 140: /** Rimuove l’elemento 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
last LinkedListIterator–remove()
Classe LinkedListIterator 159: /** 160: Sets the last traversed element to a different 161: value. 162: @param 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<E> 08: { 09: /** 10: Moves the iterator past the next element. 11: @return 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 18: @return 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. 26: @param 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. 39: @param 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 l’esempio … List<Integer> li = new ArrayList<Integer>(); List<Number> ln = li; Continua
Generics e sottotipi List<Integer> li = new ArrayList<Integer>(); List<Number> ln = li; ln.add(3.14); Integer i = li.get(0); // uh oh ... • 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 l’assegnamento al secondo Continua
Generics e sottotipi • In generale, dati due tipi A e B , ed tipo generico C<T> abbiamo che: • Quindi, per le stesse ragioni di prima A ≤ B non implica C<A> ≤ C<B> Set<Integer> non è sottotipo di Set<Object> Continua
Generics e sottotipi • Le limitazione sulle relazioni di sottotipo sono contro-intuitive • uno degli aspetti più complessi dei generics • Non solo … sono anche decisamente troppo restrittive • illustriamo con un esempio Continua
Generics e sottotipi • Un metodo che stampa gli elementi di una collezione • Versione tradizionale void printCollection(Collection c) { Iterator i = c.iterator(); for (k = 0; k < c.size(); k++) { System.out.println(i.next()); } } Continua
Generics e sottotipi • Stampa degli elementi di una collezione • Versione generics: primo tentativo • utile solo per Collection<Object> non per qualsiasi collezione • Collection<Object> non è il supertipo di tutte le collezioni void printCollection(Collection<Object> c) { for (Object e:c) System.out.println(e); } Continua
Wildcards • Stampa degli elementi di una collezione • Versione generics: secondo tentativo • Collection<?>è il supertipo di tutte leCollections • la wildcard ?indica un qualche tipo, non specificato void printCollection(Collection<?> c) { for (Object e:c) System.out.println(e); } Continua
Wildcards • Possiamo estrarre gli elementi di c al tipo Object • Corretto perché, qualunque sia il loro vero tipo, sicuramente è sottotipo di Object void printCollection(Collection<?> c) { for (Object e:c) System.out.println(e); } Continua
Wildcards • D’altra parte … • visto che non sappiamo esattamente quale tipo indica ?, non possiamo assegnare valori Collection<?> c = new ArrayList<String>(); c.add(new String()); // errore di compilazione! Continua
Domanda • Date un esempio di codice che causerebbe errore in esecuzione se permettessimo di aggiungere elementi a Collection<?>
Risposta • L’ultima istruzione invocherebbe intValue() sul primo elemento di ci • ma quell’elemento ha tipo String … • In realtà il compilatore anticipa l’errore, segnalando il problema sulla add() Collection<Integer> ci = new ArrayList<Integer>; Colletion<?> c = ci; c.add(“a string”); 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 unaList<Circle> 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<Shape> 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<? extends Shape> • bounded wildcard • indica un tipo sconosciuto, sottotipo diShape • il bound può essere qualunque tipo riferimento (classe o interfaccia) • Ora il metodo ha la flessibilità necessaria e desiderata void drawAll(List<? extends Shape> 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<? extends Shape> shapes) { for (Shape s:shapes) s.draw(this) } . . . } Continua
Bounded Wilcards • D’altra parte, c’è sempre un prezzo da pagare • Il solito vincolo … • Non possiamo modificare strutture con questi tipi [ perché? ] void addRectangle(List<? extends Shape> shapes) { // errore di compilazione shapes.add(new Rectangle()); }