Approfondimento
Interfacce e polimorfismo
Sandro Pedrazzini
Interfacce e polimorfismo
1
Motivazione
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
Sandro Pedrazzini
Interfacce e polimorfismo
2
Interface
• Separazione del concetto di interfaccia da quello di classe
• Più classi possono realizzare la stessa interfaccia
Sci
calcolaImporto(…)
Normale
calcolaImporto(…)
Sandro Pedrazzini
Carving
calcolaImporto(…)
Bambini
calcolaImporto(…)
Interfacce e polimorfismo
3
Esempio 1: genericità con l’interfaccia Icon
• Java mette a disposizione il metodo showMessageDialog(...) per
mostrare un dialogo in interfaccia
JOptionPane.showMessageDialog(null,"Hello World");
Sandro Pedrazzini
Interfacce e polimorfismo
4
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)
{...}
}
Sandro Pedrazzini
Interfacce e polimorfismo
5
Scelta dell’icona (2)
JOptionPane.showMessageDialog(
null,
"Hello World”,
“message dialog”,
JOptionPane.INFORMATION_MESSAGE,
new ImageIcon(“lampadina.gif”));
Sandro Pedrazzini
Interfacce e polimorfismo
6
Nuova 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.
Sandro Pedrazzini
Interfacce e polimorfismo
7
Interfaccia Icon (1)
public interface Icon
{
int getIconWidth();
int getIconHeight();
void paintIcon(
Component c,
Graphics g,
int x,
int y);
}
Sandro Pedrazzini
Interfacce e polimorfismo
8
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.
Sandro Pedrazzini
Interfacce e polimorfismo
9
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.
Sandro Pedrazzini
Interfacce e polimorfismo
10
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.
Sandro Pedrazzini
Interfacce e polimorfismo
11
Implementazione (2)
Icon
paintIcon(…)
ImageIcon
paintIcon(…)
Sandro Pedrazzini
WorldIcon
paintIcon(…)
Interfacce e polimorfismo
12
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;
}
}
Sandro Pedrazzini
Interfacce e polimorfismo
13
Utilizzo
JOptionPane.showMessageDialog(
null,"Hello World", "message dialog",
JOptionPane.INFORMATION_MESSAGE,
new WorldIcon(60));
Sandro Pedrazzini
Interfacce e polimorfismo
14
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.
Sandro Pedrazzini
Interfacce e polimorfismo
15
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.
Sandro Pedrazzini
Interfacce e polimorfismo
16
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”
Sandro Pedrazzini
Interfacce e polimorfismo
17
Esempio 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);
Sandro Pedrazzini
Interfacce e polimorfismo
18
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
Sandro Pedrazzini
Interfacce e polimorfismo
19
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
}
Sandro Pedrazzini
Interfacce e polimorfismo
20
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);
...
Sandro Pedrazzini
Interfacce e polimorfismo
21
Comparable (4)
Esempio 2: Realizzazione di una nuova classe
• Criterio di ordinamento: dimensione della superficie del territorio
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 int compareTo(Country other) {
if (fArea < other.getArea()){
return -1;
}
if (fArea > other.getArea()){
return 1;
}
return 0;
}
public double getArea() {
return fArea;
}
}
Sandro Pedrazzini
Interfacce e polimorfismo
22
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());
}
}
}
Sandro Pedrazzini
Interfacce e polimorfismo
23
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)
Sandro Pedrazzini
Interfacce e polimorfismo
24
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) {
...
}
}
Sandro Pedrazzini
Interfacce e polimorfismo
25
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;
}
}
Sandro Pedrazzini
Interfacce e polimorfismo
26
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
}
}
Sandro Pedrazzini
Interfacce e polimorfismo
27
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;
}
}
Sandro Pedrazzini
Interfacce e polimorfismo
28
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());
}
}
Sandro Pedrazzini
Interfacce e polimorfismo
29
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.
Sandro Pedrazzini
Interfacce e polimorfismo
30
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
Sandro Pedrazzini
Interfacce e polimorfismo
31
Oggetti 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ì)
Sandro Pedrazzini
Interfacce e polimorfismo
32
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;
}
}
Sandro Pedrazzini
Interfacce e polimorfismo
33
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
Sandro Pedrazzini
Interfacce e polimorfismo
34
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;
}
Sandro Pedrazzini
Interfacce e polimorfismo
35
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
Sandro Pedrazzini
Interfacce e polimorfismo
36
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
Sandro Pedrazzini
Interfacce e polimorfismo
37
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
Sandro Pedrazzini
Interfacce e polimorfismo
38
Esempio
public 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;
}
}
Sandro Pedrazzini
Interfacce e polimorfismo
39