Slides
Transcript
Slides
Programmazione Parametrica ( a.k.a. Generics ) • Programmazione parametrica: introduzione • Generics e relationi di sottotipo wildcards generics e vincoli • Implementazione di classi e metodi parametrici • Supporto per i generics nella JVM • Collections 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” public class Set<E> { public ArrayList() { . . . } public void add(E element) { . . . } . . . } • E = variabile di tipo astrae (e rappresenta) il tipo delle componenti Continua Variabili di Tipo • Possono essere istanziate con tipi classe o interfaccia ArrayList<BankAccount> ArrayList<Measurable> • Vincolo: tipi che istanziano variabili di tipo non possono essere primitivi (devono essere tipi riferimento) ArrayList<double> // No! • Classi wrapper utili allo scopo 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 List intList = new LinkedList(); intList.add(new Integer(57)); Integer x = (Integer) intList.get(0); • 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 Continua Variabili di tipo e controlli di tipo • Container generici: più sintetici ed eleganti List<Integer> intList = new LinkedList<Integer>(); intList.add(new Integer(0)); Integer x = intList.get(0); • Compilatore può stabilire un invariante sugli elementi della lista garantire l’assenza di errori a run-time in forza di quell’invariante. Continua Variabili di tipo e controlli di tipo List<Integer> intList = new LinkedList<Integer>(); intList.add(new Integer(0)); Integer x = intList.get(0); • 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 Classi parametriche: uso • Usare un tipo parametrico = istanziarlo per creare riferimenti e oggetti List<Integer> intList = new LinkedList<Integer>(); tutte le occorrenze dei parametri formali sono rimpiazzate dall’argomento (parametro attuale) • Diversi usi generano tipi diversi • Ma . . . classi parametriche compilate una sola volta danno luogo ad un unico file .class 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: Pair<String, BankAccount> result = new Pair<String, BankAccount> ("Harry Hacker", harrysChecking); • I metodi getFirst e getSecond restituiscono il primo e secondo elemento, con i tipi corrispondenti String name = result.getFirst(); BankAccount account = result.getSecond(); Variabili di tipo: convenzioni Variabile Significato Inteso E Tipo degli elementi in una collezione K Tipo delle chiavi in una mappa V Tipo dei valori in una mappa T,S,U Tipi generici 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> • Notiamo la struttura della classe Node se la classe è interna, come nell’esempio, 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 Esempio: LinkedList<E> 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 Generics e sottotipi • I meccanismi di subtyping si estendono alle classi generiche class C<T> implements / extends D<T> { . . . } • C<T> <: D<T> per qualunque T • Analogamente: class C<T> implements / estends D { . . . } • C<T> <: D per qualunque T • Sembra tutto facile, MA . . . Generics e sottotipi • Consideriamo List<Integer> li = new ArrayList<Integer>(); List<Number> ln = li; • La prima istruzione è legale, la seconda è più delicata … Number è una classe che ha Integer , Double e altre classi wrapper come sottotipi Per capire se la seconda istruzione sia da accettare continuiamo con l’esempio … Continua Generics e sottotipi List<Integer> li = new ArrayList<Integer>(); List<Object> lo = li; // type error lo.add(“uh oh”)); Integer i = li.get(0); // uh oh ... • Problema nella terza istruzione inseriamo un Double nella quarta estraiamo un Integer ! • Errore è nella seconda istruzione soluzione: errore di compilazione per l’assegnamento Continua Generics e sottotipi • In generale: A ≤ B NON implica C<A> ≤ C<B> • Quindi, ad esempio: Set<Integer> NON è sottotipo di Set<Object> • Come dimostrato, vincoli necessari per la correttezza del principio di sostituibilità Generics e sottotipi • Limitazione sul subtyping con generics contro-intuitive uno degli aspetti più complessi dei generics • Spesso anche troppo restrittive illustriamo con un esempio Continua Generics e sottotipi • Stampa gli elementi di una qualunque collection • Primo tentativo static void printCollection(Collection<Object> els) { for (Object e:els) System.out.println(e); els.add(“pippo”); // ok } Inutile: Collection<Object> non è il supertipo di Collection<T> per alcun T != Object Continua Wildcards • Stampa degli elementi di una collezione • Secondo tentativo static void printCollection(Collection<?> els) { for (Object e:els) System.out.println(e); els.add(“pippo”); // ko } Collection<?> è supertipo di qualunque Collection<T> Wildcard ? indica un qualche tipo, non specificato Continua Wildcards void printCollection(Collection<?> els) { for (Object e:els) System.out.println(e); } • Possiamo estrarre gli elementi di els al tipo Object • Corretto perché, qualunque sia il loro vero tipo, sicuramente è sottotipo di Object Continua Wildcards • Però … Collection<?> c = new ArrayList<String>(); c.add(new String()); // errore di compilazione! • Poichè non sappiamo esattamente quale tipo indica ?, non possiamo inserire elementi nella collezione • In generale, non possiamo modificare valori che hanno tipo ? Continua Domanda • Date un esempio di codice che causerebbe errore in esecuzione se permettessimo di aggiungere elementi a Collection<?> Risposta Collection<Integer> ci = new ArrayList<Integer>; Colletion<?> c = ci; c.add(“a string”); // non compila ci.get(0).intValue(); • L’ultima istruzione invocherebbe intValue() sul primo elemento di ci • ma quell’elemento ha tipo String … • Il compilatore previene l’errore, rigettando la add() Wilcards con vincoli (bounded) • Shapes: (again!) interface Shape { public void draw(Graphics g); } class Circle extends Shape { private int x, y, radius; public void draw(Graphics g) { ... } } class Rectangle extends Shape { private int x, y, width, height; public void draw(Graphics g) { ... } } Wilcards con vincoli (bounded) • 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 static void drawAll(List<Shape> shapes) { for (Shape s:shapes) s.draw(this) } . . . } • Solito problema: drawAll() non può essere invocato su una List<Circle> Continua Bounded Wilcards • Quello che ci serve è un metodo che accetti liste di qualunque (sotto) tipo di Shape void drawAll(List<? extends Shape> shapes) { ... } • List<? extends Shape> 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 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 • Attenzione: c’è sempre un prezzo da pagare void addRectangle(List<? extends Shape> shapes) { // errore di compilazione shapes.add(new Rectangle()); } • Non possiamo modificare strutture con questi tipi [ perché? ] Metodi Generici Metodi Generici • Metodi che dipendono da una variabile di tipo • Possono essere definiti all’interno di qualunque classe, generica o meno /* trasforma un array in una lista, copiando * tutti gli elementi di a in l */ static void array2List(Object[] a, List<?> l){ . . . } • N.B. Evitiamo List<Object> perché renderebbe il metodo non utilizzabie su liste arbitrarie Continua Metodi Generici • Al solito però . . . /* 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 } • . . . non possiamo aggiungere elementi ad una struttura (o modificare) con elementi di tipo wildcard Continua Metodi Generici • Soluzione: rendiamo il metodo parametrico /* trasforma un array in una lista, copiando * tutti gli elementi di a in l */ static <T> void array2List(T[] a, List<T> l) { for (T o : a) l.add(o) } • possiamo invocare questo metodo con una qualunque lista il cui tipo sia supertipo del tipo base dell’array purché sia un tipo riferimento Invocazione di metodi generici • Nell’invocazione di un metodo generico non è necessario passare l’argomento di tipo • il compilatore inferisce il tipo, se esiste, dai tipi degli argomenti del metodo Invocazione di metodi generici • Object[] oa = new Object[100]; Collection<Object> co = new ArrayList<Object>(); fromArrayToCollection(oa, co); // T = Object (inferito) String[] sa = new String[100]; Collection<String> cs = new ArrayList<String>(); 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<Number> cn = new ArrayList<Number>(); 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 Continua Wildarcds vs variabili di tipo • Ci sono situazioni in cui è possibili usare equivalentemente wildcards e variabili di tipo. • Nella libreria Collection troviamo interface Collection<E> { public boolean containsAll(Collection<?> c); public boolean addAll(Collection<? extends E> c); . . . } Continua Wildarcds vs variabili di tipo • Queste specifiche possono essere espresse equivalentemente con metodi parametrici interface Collection<E> { public <T> boolean containsAll(Collection<T> c); public <T extends E> boolean addAll(Collection<T> c); . . . } • Il secondo metodo è parametrico in qualunque sottotipo di E i bounds si possono utilizzare anche con variabili, non solo con wildcards Continua Wildarcds vs variabili di tipo • Wildcards e variabili di tipo possono coesistere interface Collection<E> { public static <T> void copy(List<T> dest, List<? extends T> src) . . . } • Notiamo la dipendenza tra i tipi dei due parametri: il tipo della sorgente deve essere un sottotipo del tipo della destinazione Continua Wildarcds vs variabili di tipo • Potremmo analogamente riformulare in modo da evitare le wildcards interface Collection<E> { public static <T, S extends T> void copy(<List<T> dest, List<S> src) . . . } • Come scegliere tra le due soluzioni? Continua Wildarcds vs variabili di tipo • 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 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 extends Comparable<T>> max(Collection<T> 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 public static <T extends Comparable<T>> T max(List <T> coll) • 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 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<String> words = new ArrayList<String>(); 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 che vengono aggiunti dalla compilazione di codice generico non falliscono mai. Generics e Arrays • In generale: A ≤ B NON implica C<A> ≤ C<B> • MA: A ≤ B implica A[] ≤ B[] • Quali conseguenze? Generics e Arrays • Conseguenze / 1: ArrayStoreException Integer[] ai = new Integer[10] Number[] an = ai; // type OK an[0] = 3.14; // ArrayStoreException • Meglio così che continuare Integer i = ai[0]; // uh oh ... Generics e Arrays • Conseguenze / 2: no new per array generici class MyClass<T> { T[] contents = new T[100]; // Non compila // ecco perchè public void showTheProblem() { Object[] objs = contents; objs[0] = new String(); // no ArrayStoreException T bump = contents[0]; // ClassSclassException // per T != String } } Collections