Post on 02-May-2015
transcript
1 Interfacce e polimorfismoSandro Pedrazzini
Approfondimento
Interfacce e polimorfismo
Approfondimento
Interfacce e polimorfismo
2 Interfacce e polimorfismoSandro Pedrazzini
MotivazioneMotivazione
Importanza del polimorfismo nell’utilizzo dei pattern e, piùIn generale, nella programmazione OO
Ruolo degli elementi “interface”
Disaccoppiamento degli elementi
Programmazione generica
Estensione di funzionalità esistente
3 Interfacce e polimorfismoSandro Pedrazzini
InterfaceInterface
• Separazione del concetto di interfaccia da quello di classe
• Più classi possono realizzare la stessa interfaccia
Sci
calcolaImporto(…)
Carving
calcolaImporto(…)
Bambini
calcolaImporto(…)
Normale
calcolaImporto(…)
4 Interfacce e polimorfismoSandro Pedrazzini
Esempio 1: genericità con l’interfaccia IconEsempio 1: genericità con l’interfaccia Icon
• Java mette a disposizione il metodo showMessageDialog(...) per mostrare un dialogo in interfaccia
JOptionPane.showMessageDialog(null,"Hello World");
5 Interfacce e polimorfismoSandro Pedrazzini
Scelta dell’icona (1)Scelta dell’icona (1)
• Esistono altri overloading del metodo showMessageDialog(...). Ne scegliamo uno che ci permetta di specificare cosa mostrare come immagine nel messaggio.
class JOptionPane extends JComponent implements Accessible{ ... public static void showMessageDialog( Component parentComponent, Object message, String title, int messageType, Icon icon) {...}
}
6 Interfacce e polimorfismoSandro Pedrazzini
Scelta dell’icona (2)Scelta dell’icona (2)
JOptionPane.showMessageDialog(null,"Hello World”,“message dialog”,JOptionPane.INFORMATION_MESSAGE,new ImageIcon(“lampadina.gif”));
7 Interfacce e polimorfismoSandro Pedrazzini
Nuova sceltaNuova scelta
• Supponiamo ora di voler visualizzare nel messaggio una forma grafica senza dover generare precedentemente un file contenente l’immagine.
• Visto che showMessageDialog(...) accetta un elemento di tipo Icon, non siamo obbligati a passare ImageIcon, ma possiamo fornire un oggetto di qualsiasi classe che realizzi l’interfaccia Icon.
8 Interfacce e polimorfismoSandro Pedrazzini
Interfaccia Icon (1)Interfaccia Icon (1)
public interface Icon{ int getIconWidth(); int getIconHeight(); void paintIcon( Component c, Graphics g, int x, int y);}
9 Interfacce e polimorfismoSandro Pedrazzini
Interfaccia Icon (2)Interfaccia Icon (2)
• Un’interfaccia non contiene una realizzazione di funzionalità. Specifica semplicemente un insieme di metodi.
• Qualsiasi classe che implementi l’interfaccia Icon ha due responsabilità:
– Fornire la dimensione dell’icona
– Disegnare l’icona.
10 Interfacce e polimorfismoSandro Pedrazzini
Interfaccia Icon (3)Interfaccia Icon (3)
• Il parametro Component di paintIcon() rappresenta il componente grafico che deve contenere l’immagine. Da questo è possibile ottenere alcune proprietà, come il colore dello sfondo, il font, ecc.
• È quindi possibile disegnare l’immagine nell’area grafica (Graphics) in modo che si adatti al contesto in cui viene disegnata.
11 Interfacce e polimorfismoSandro Pedrazzini
Implementazione (1)Implementazione (1)
• Realizziamo una classe WorldIcon che implementa Icon.
• La classe disegna un cerchio rappresentante il mondo.
• Un oggetto di questa classe potrà essere passato al metodo showMessageDialog(), che non ha bisogno di conoscere la classe WorldIcon. Gli basta sapere che si comporta come (implementa) Icon.
12 Interfacce e polimorfismoSandro Pedrazzini
Implementazione (2)Implementazione (2)
Icon
paintIcon(…)
WorldIcon
paintIcon(…)
ImageIcon
paintIcon(…)
13 Interfacce e polimorfismoSandro Pedrazzini
Implementazione (2)Implementazione (2)
public class WorldIcon implements Icon{ private int fSize;
public WorldIcon(int size){ fSize = size; }
public void paintIcon(Component c, Graphics g, int x, int y) { Graphics2D g2 = (Graphics2D)g; Ellipse2D.Double world = new Ellipse2D.Double(x,y,fSize, fSize); g2.setColor(Color.BLUE); g2.fill(world); }
public int getIconWidth() { return fSize; }
public int getIconHeight() { return fSize; }}
14 Interfacce e polimorfismoSandro Pedrazzini
UtilizzoUtilizzo
JOptionPane.showMessageDialog( null,"Hello World", "message dialog", JOptionPane.INFORMATION_MESSAGE, new WorldIcon(60));
15 Interfacce e polimorfismoSandro Pedrazzini
Considerazioni (1)Considerazioni (1)
• Chi realizza il metodo showMessageDialog() non ha nessuna idea di quale tipo di icona verrà passata.
• Le classi utilizzate per realizzare l’icona possono essere completamente diverse. L’unica cosa in comune consiste nell’implementare l’interfaccia Icon.
• Solo quando viene chiamato in showMessageDialog() un metodo di Icon, l’interprete Java cerca di identificare il vero tipo dell’oggetto.
16 Interfacce e polimorfismoSandro Pedrazzini
Considerazioni (2)Considerazioni (2)
• Il polimorfismo è caratterizzato proprio da questa capacità di selezionare il metodo appropriato per un certo oggetto.
• Un utilizzo importante del polimorfismo consiste nel fornire meccanismi che si comportino come accoppiamento rilassato.
• Nel nostro caso: il metodo showMessageDialog() non ha bisogno di nessuna informazione di come WorldIcon elabori l’immagine. È solamente interessato alle chiamate all’interfaccia Icon. Non esiste nessun accoppiamento tra JOptionPane e WorldIcon.
17 Interfacce e polimorfismoSandro Pedrazzini
Considerazioni (3)Considerazioni (3)
• Quando utilizziamo la classe di una libreria di terzi, prima viene implementata la libreria, poi il nostro programma principale che la utilizza.
• Quando creiamo una nostra sottoclasse da usare in modo polimorfico, il programma principale che la utilizza può essere stato implementato da terzi ben prima che noi mettiamo a disposizione la nostra funzionalità specifica
=> Principio del “framework”
18 Interfacce e polimorfismoSandro Pedrazzini
Esempio 2: ComparableEsempio 2: Comparable
• Altro esempio di codice generico con l’utilizzo del polimorfismo
• Metodo statico sort() della classe Collections, in grado di ordinare una lista qualsiasi.
List list = ...;Collections.sort(list);
19 Interfacce e polimorfismoSandro Pedrazzini
Comparable (1)Comparable (1)
• I singoli oggetti della lista possono appartenere a una classe qualsiasi, a patto che implementi l’interfaccia Comparable.
public interface Comparable<T>{ int compareTo(T other);}
• La chiamata a compareTo() restituisce un valore negativo se l’oggetto invocante precede l’oggetto parametro, zero se i due oggetti sono uguali e un valore positivo
20 Interfacce e polimorfismoSandro Pedrazzini
Comparable (2)Comparable (2)
• Come mai tutti gli oggetti devono essere di tipo Comparable?
• Perché l’algoritmo di sort, chiamando compareTo() riesce a decidere gli spostamenti degli oggetti, senza dover conoscere il loro vero tipo.
Comparable<...> object1 = ...;if (object1.compareTo(object2) > 0){ sposta object1 rispetto a object2}
21 Interfacce e polimorfismoSandro Pedrazzini
Comparable (3)Comparable (3)
Esempio 1: String realizza Comparable
List<String> countries = new ArrayList<String>();countries.add(“Switzerland);countries.add(“Belgium”);countries.add(“Germany”);
Collections.sort(countries);...
22 Interfacce e polimorfismoSandro Pedrazzini
Comparable (4)Comparable (4)Esempio 2: Realizzazione di una nuova classe
public class Country implements Comparable<Country> { private String fName; private double fArea;
public Country(String name, double area){ fName = name; fArea = area; }
public String getName() { return fName; }
public double getArea() { return fArea; }
• Criterio di ordinamento: dimensione della superficie del territorio
public int compareTo(Country other) { if (fArea < other.getArea()){ return -1; } if (fArea > other.getArea()){ return 1; } return 0; }}
23 Interfacce e polimorfismoSandro Pedrazzini
Comparable (5)Comparable (5)
• Utilizzo della classe Country, con ordinamento secondo la grandezza in km2
public class CountryTry { public static void main(String[] args) { List<Country> countries = new ArrayList<Country>(); countries.add(new Country("Belgium",77000)); countries.add(new Country("Switzerland",41000)); countries.add(new Country("Uruguay",440000)); ... Collections.sort(countries);
for (Country country : countries){ System.out.println(country.getName() + " " + country.getArea()); } }}
24 Interfacce e polimorfismoSandro Pedrazzini
Interfaccia Comparator (1)Interfaccia Comparator (1)
• Se ora volessimo ordinare le stesse nazioni dell’esempio 2 in base al nome, invece che in base alla superficie, dovremmo ridefinire il metodo compareTo().
• Oltre che essere scomodo, ci obbligherebbe a definire sottoclassi unicamente per distinguere diversi metodi compareTo() (modifica del design per scopi che con il design nulla hanno a che vedere)
25 Interfacce e polimorfismoSandro Pedrazzini
Interfaccia Comparator (2)Interfaccia Comparator (2)
• Soluzione: utilizzo di un overloading di sort(), che accetta come secondo parametro un oggetto Comparator
• Gli oggetti presenti in List vengono ordinati in base all’ordinamento definito in Comparator.
class Collections { ... public static void sort(List<T> list, Comparator<? super T> c) { ... }}
26 Interfacce e polimorfismoSandro Pedrazzini
Interfaccia Comparator (3)Interfaccia Comparator (3)
• La lista List può ora contenere oggetti di qualsiasi tipo.
• Non è più necessario che appartengano a classi che implementino un’interfaccia particolare.
public class Country { private String fName; private double fArea;
public Country(String name, double area){ fName = name; fArea = area; }
public String getName() { return fName; }
public double getArea() { return fArea; }}
27 Interfacce e polimorfismoSandro Pedrazzini
Interfaccia Comparator (4)Interfaccia Comparator (4)
public class ComparatorByName implements Comparator<Country> { public int compare(Country country1, Country country2) { String name1 = country1.getName(); String name2 = country2.getName();
return name1.compareTo(name2); //implementato in String }}
28 Interfacce e polimorfismoSandro Pedrazzini
Interfaccia Comparator (5)Interfaccia Comparator (5)
public class ComparatorByArea implements Comparator<Country> { public int compare(Country country1, Country country2) { String area1 = country1.getArea(); String area2 = country2.getArea();
if (area1 < area2){ return -1; } if (area1 > area2){ return 1; }
return 0; }}
29 Interfacce e polimorfismoSandro Pedrazzini
Interfaccia Comparator (6)Interfaccia Comparator (6)
• Utilizzo di ComparatorByName
public class CountryTry2 { public static void main(String[] args) { List<Country> countries = new ArrayList<Country>(); countries.add(new Country("Switzerland", 15000)); countries.add(new Country("Uruguay", 170000)); countries.add(new Country("Belgium", 30000)); ... Collections.sort(countries, new ComparatorByName());
for (Country country : countries) { System.out.println(country.getName() + " " + country.getArea()); } }}
30 Interfacce e polimorfismoSandro Pedrazzini
Overriding di equals() e hashCode() (1)Overriding di equals() e hashCode() (1)
• Una comune sorgente di errore in applicazioni Java consiste nel dimenticare di riscrivere il metodo hashCode() ogni volta che si riscrive equals().
• Il metodo hashCode() viene usato quando si ha a che fare con elementi di Collection, come liste e tabelle hash.
31 Interfacce e polimorfismoSandro Pedrazzini
Overriding di equals() e hashCode() (2)Overriding di equals() e hashCode() (2)
• Il contratto, come specificato in Object, prevede
1. hashCode() deve restituire sempre lo stesso valore se chiamato più volte sullo stesso oggetto (non necessariamente lo stesso tra un’esecuzione e l’altra)
2. Se due oggetti sono uguali rispetto a equals(), anche i loro metodi hasCode() devono restituire lo stesso risultato
3. Se due oggetti sono diversi, rispetto a equals(), non è detto che hashCode() debba restituire risultati diversi. Se però si fa in modo che anche le hashCode() restituiscono risultati diversi, anche la performance di tabelle hash migliora
32 Interfacce e polimorfismoSandro Pedrazzini
Oggetti uguali, stesso hashCodeOggetti uguali, stesso hashCode
• L’errore più frequente capita con il punto 2: oggetti uguali devono avere lo stesso valore di hash
– Capita infatti che due oggetti siano uguali dal punto di vista logico (overriding di equals()), ma dal loro valore di hash sono diversi
– Il metodo hashCode() viene ereditato da Object, che assegna un hashCode diverso ad ogni nuovo oggetto (tipicamente convertendo l’indirizzo di allocazione in un int, anche se non dev’essere necessariamente così)
33 Interfacce e polimorfismoSandro Pedrazzini
Esempio (1)Esempio (1)public class Country { private String fName; private double fArea;
public Country(String name, double area){ fName = name; fArea = area; }
public String getName() { return fName; }
public double getArea() { return fArea; }
public boolean equals(Object o) { if (o == this){ return true; } if (!o instanceof Country) { return false; } Country county = (Country)o; return country.getName().equals(fName) && country.getArea() == fArea; }}
34 Interfacce e polimorfismoSandro Pedrazzini
Esempio (2)Esempio (2)
• Supponiamo ora di voler usare questa classe con HashMap
Map<Country, Integer> population = new HashMap<Country, Integer>();population.put(new Country(“Switzerland”, 15000), 7000000);
Ci si aspetterebbe che un’espressione del genere restituisca il valore di 7 milioni, invece restituisce null
population.get(new Country(“Switzerland”, 15000));
gli oggetti coinvolti sono 2 e il fatto di aver omesso l’implementazione di hashCode() causa un diverso valore per istanze diverse
35 Interfacce e polimorfismoSandro Pedrazzini
Esempio (3)Esempio (3)
• Bisogna allora provvedere a fornire un hashCode che sia uguale per istanze equals
• Variante semplice, ma da non usare !!
public int hashCode() {
return 150;
}
36 Interfacce e polimorfismoSandro Pedrazzini
Esempio (4)Esempio (4)
• Un codice fisso è “legale” perché assicura che oggetti uguali abbiano lo stesso hashCode
• Non è una buona soluzione, perché fa in modo che TUTTI gli oggetti abbiano hashCode uguale.
• In questo modo tutti gli oggetti in una hash table avrebbero stessa chiave, facendo degenerare la tabella in una lista semplice, con i costi che ne derivano
37 Interfacce e polimorfismoSandro Pedrazzini
hashCode (1)hashCode (1)
• Una buona funzione di hash tende a produrre valori diversi di hash per oggetti diversi
• Ci sono varie ricette su come generare valori di hashCode
• Alcuni elementi da considerare:– Cercare di considerare tutti i campi significativi usati in equals()
– Tralasciare i campi ridondanti (calcolati da altri campi)
– Se un campo è un array, trattarlo come se ogni elemento fosse un campo separato
38 Interfacce e polimorfismoSandro Pedrazzini
hashCode (2)hashCode (2)
• Esempio tratto da Bloch, Effective Java
– Registra una costante iniziale nella variabile risultato (esempio: 17)– Per ogni campo genera un valore intero
» Per ogni elemento di tipo scalare, usare il valore effettivo» byte, char, short, int => (int) f» float => Float.floatToIntBits(f)» long => (int) (f^(f >>> 32) (xor con shift a destra di 4 byte)» Per riferimenti a oggetti che in equals() sono usati attraverso il loro equals(), usare il loro valore di
hashCode()– Combinare tutti i valori ottenuti in questo modo:
risultato = 31 * risultato + valore
Verificare che a istanze uguali corrispondano valori uguali
Il valore 31 è scelto perché numero primo. Un vantaggio di 31 consiste nel fatto che la moltiplicazione può essere rimpiazzata da uno shift e sottrazione, più performanti.
31 * i == (i << 5) -i
Moderne VM eseguono automaticamente questo tipo di ottimizzazione
39 Interfacce e polimorfismoSandro Pedrazzini
EsempioEsempiopublic class Country { private String fName; private double fArea;
...
public boolean equals(Object o) { if (o == this){ return true; } if (!o instanceof Country) { return false; } Country county = (Country)o; return country.getName().equals(fName) && country.getArea() == fArea; }
public int hashCode() { int result = 17; result = 31 * result + fName.hashCode(); long areaCode = Double.doubleToLongBits(fArea); result = 31 * result + (int)(areaCode ^(areaCode >>> 32)); return result; }}