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 E K V T,S,U Significato Inteso Tipo degli elementi in una collezione Tipo delle chiavi in una mappa Tipo dei valori in una mappa 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