1 / 67

Programmazione Parametrica ( a.k.a. Generics )

Programmazione Parametrica ( a.k.a. Generics ). Introduzione ai meccanismi e concetti della programmazione parametrica Generics e relationi di sottotipo wildcards generics e vincoli Implementazione di classi e metodi parametrici Supporto per i generics nella JVM.

carson
Download Presentation

Programmazione Parametrica ( a.k.a. Generics )

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Programmazione Parametrica(a.k.a.Generics )

  2. Introduzione ai meccanismi e concetti della programmazione parametrica • Generics e relationi di sottotipo • wildcards • generics e vincoli • Implementazione di classi e metodi parametrici • Supporto per i generics nella JVM

  3. 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)

  4. 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

  5. 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>

  6. 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

  7. 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.get(0); Continua

  8. 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.get(0); Continua

  9. 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.get(0);

  10. 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<F> { F next(); boolean hasNext(); }

  11. 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

  12. Sintassi: uso GenericClassName<Type1, Type2, . . .> Esempio:   ArrayList<BankAccount> HashMap<String, Integer> Scopo: Fornire tipo specifici per ciascuna delle variabili di tipo introdotte nella dichiarazione

  13. 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

  14. 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();

  15. Variabili di tipo: convenzioni

  16. 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

  17. 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

  18. 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

  19. Generics e sottotipi • I meccanismi di subtyping si estendono alle classi generiche • C<T> <: I<T> per qualunque T • Analogamente: • C<T> <: I per qualunque T • Sembra tutto facile, MA . . . class C<T> implements I<T> { . . . } class C<T> implements I { . . . }

  20. Generics e sottotipi • Consideriamo • 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 come legale continuiamo con l’esempio … List<Integer> li = new ArrayList<Integer>(); List<Number> ln = li; Continua

  21. Generics e sottotipi List<Integer> li = new ArrayList<Integer>(); List<Number> ln = li; // type error ln.add(3.14); Integer i = li.get(0); // uh oh ... • Come si vede abbiamo un problema • nella terza istruzione inseriamo un Double • nella quarta estraiamo un Integer ! • Il vero problema è nella seconda istruzione • soluzione: errore di compilazione per l’assegnamento Continua

  22. 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 • Come abbiamo visto questo è necessario per garantire la correttezza A ≤ BNON implica C<A> ≤ C<B> Set<Integer>NON è sottotipo di Set<Object> Continua

  23. Generics e sottotipi • In generale, dati due tipi A e B , ed tipo generico C<T> abbiamo che: • MA … A ≤ BNON implica C<A> ≤ C<B> A ≤ B implica A[] ≤ B[] Continua

  24. Generics e sottotipi Integer[] ai = new Integer[10] Number[] an = ai; // type OK an[0] = 3.14; // ArrayStoreException Integer i = ai[0]; // uh oh ... Continua

  25. 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

  26. Generics e sottotipi • Stampa degli elementi di una collezione • Primo tentativo • Inutile per stampare gli elementi di una generica Collection<T> • Collection<Object> non è il supertipo di tutte le collezioni static void printCollection(Collection<Object> c) { for (Object e:c) System.out.println(e); } Continua

  27. Wildcards • Stampa degli elementi di una collezione • Secondo tentativo • Collection<?>è il supertipo di tutte leCollections • la wildcard ?indica un qualche tipo, non specificato static void printCollection(Collection<?> c) { for (Object e:c) System.out.println(e); } Continua

  28. 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

  29. Wildcards • D’altra parte … • Poichè non sappiamo esattamente quale tipo indica ?, non possiamo inserire elementi nella collezione • In generale, non possiamo modificare valori che hanno tipo ? Collection<?> c = new ArrayList<String>(); c.add(new String()); // errore di compilazione! Continua

  30. Domanda • Date un esempio di codice che causerebbe errore in esecuzione se permettessimo di aggiungere elementi a Collection<?>

  31. Risposta • L’ultima istruzione invocherebbe intValue() sul primo elemento di ci • ma quell’elemento ha tipo String … • Il compilatore previene l’errore, rigettando la add() Collection<Integer> ci = new ArrayList<Integer>; Colletion<?> c = ci; c.add(“a string”); // non compila ci.get(0).intValue();

  32. 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) { ... } } Continua

  33. 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

  34. 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

  35. 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

  36. Bounded Wilcards • Attenzione: c’è sempre un prezzo da pagare • Non possiamo modificare strutture con questi tipi [ perché? ] void addRectangle(List<? extends Shape> shapes) { // errore di compilazione shapes.add(new Rectangle()); }

  37. Metodi Generici • Metodi che dipendono da una variabile di tipo • Possono essere definiti all’interno di qualunque classe, generica o meno • N.B. Evitiamo List<Object> perché renderebbe il metodo 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){ . . . } Continua

  38. 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

  39. Metodi Generici • Soluzione: rendiamo il metodo parametrico • possiamo invocare questo metodo con una qualunque lista il cui tipo sia supertipo del tipo base dell’array • purché sia un tipo riferimento /* 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) }

  40. 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

  41. 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

  42. 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); public boolean addAll(Collection<E> c); . . . } Continua

  43. 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 interface Collection<E> { public <T> boolean containsAll(Collection<T> c); public <T extends E> boolean addAll(Collection<T> c); . . . } Continua

  44. Wildarcds vs variabili di tipo • 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 interface Collection<E> { public static <T> void copy(List<T> dest, List<? extends T> src) . . . } Continua

  45. Wildarcds vs variabili di tipo • Potremmo analogamente riformulare in modo da evitare le wildcards • Come scegliere tra le due soluzioni? interface Collection<E> { public static <T, S extends T> void copy(<List<T> dest, List<S> src) . . . } Continua

  46. 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

  47. 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.

  48. 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);

  49. Generics e “erasure” • Cast-iron guarantee • i cast impliciti che vengono aggiunti dalla compilazione di codice generico non falliscono mai.

  50. Generics e Array • Non è possibile creare array generici • Ecco perché: class MyClass<T> { T[] contents = new T[100]; // Non compila public void showTheProblem() { Object[] objs = contents; objs[0] = new String(); // no ArrayStoreException T bump = contents[0]; // ClassSclassException } }

More Related