• Non ci sono risultati.

Ordinamento di oggetti

N/A
N/A
Protected

Academic year: 2021

Condividi "Ordinamento di oggetti"

Copied!
30
0
0

Testo completo

(1)

1

Riflessione:

Ordinamento di oggetti

SETTIMANA 7

2

Ordinamento di oggetti

Si sono visti algoritmi di ordinamento su array di numeri

Ma spesso bisogna ordinare dati più complessi

 ad esempio stringhe, ma anche oggetti di altro tipo

Gli algoritmi di ordinamento che abbiamo esaminato   effettuano confronti tra numeri

 Si possono usare gli stessi algoritmi per ordinare oggetti, a  patto che questi siano tra loro confrontabili

C’è però una differenza

 confrontare numeri ha un significato matematico ben  definito

 confrontare oggetti ha un significato che dipende dal tipo  di oggetto, e a volte può non avere significato alcuno

Ordinamento di oggetti

Confrontare oggetti ha un significato che dipende dal  tipo di oggetto

 quindi la classe che definisce l’oggetto deve anche  definire il significato del confronto

Consideriamo la classe String

 essa definisce il metodo compareTo che attribuisce  un significato ben preciso all’ordinamento tra stringhe

l’ordinamento lessicografico

Possiamo quindi riscrivere, ad esempio, il metodo  selectionSort per ordinare stringhe invece di  ordinare numeri, senza cambiare l’algoritmo

SelectionSort per stringhe

public class ArrayAlgs{...

public static void selectionSort(String[] v, int vSize) {

for (int i = 0; i < vSize - 1; i++)

{ int minPos = findMinPos(v, i, vSize-1);

if (minPos != i) swap(v, minPos, i);

}

} //abbiamo usato due metodi ausiliari, swap e findMinPos private static void swap(String[] v, int i, int j)

{ String temp = v[i];

v[i] = v[j];

v[j] = temp;

}

private static int findMinPos(String[] v, int from, int to) { int pos = from;

String min = v[from];

for (int i = from + 1; i <= to; i++) if (v[i].compareTo(min) < 0) { pos = i;

min = v[i]; } return pos;

} ...

(2)

5

Ordinamento di oggetti

Si possono riscrivere tutti i metodi di ordinamento e  ricerca visti per i numeri interi ed usarli per le stringhe

Ma come fare per altre classi?

Possiamo ordinare oggetti di tipo BankAccount in ordine,  ad esempio, di saldo crescente?

Bisogna definire nella classe BankAccount un metodo  analogo al metodo compareTo della classe String

Bisogna riscrivere i metodi perché accettino come  parametro un array di BankAccount

Non possiamo certo usare questo approccio per  qualsiasi classe, deve esserci un sistema migliore!

 In effetti c'è, ma dobbiamo prima studiare l'ereditarietà e  l'uso di interfacce in Java ...

 ... poi torneremo sul problema di ordinare oggetti generici

6

Ereditarietà (capitolo 10)

L’ereditarietà

L’ereditarietà è uno dei principi basilari della  programmazione orientata agli oggetti

L’ereditarietà è un paradigma che supporta l’obiettivo  di riusabilità del codice

 si usa quando si deve realizzare una classe ed è già  disponibile un’altra classe che rappresenta un 

concetto più generale

(3)

9

L’ereditarietà

Supponiamo di voler realizzare una classe  SavingsAccount per rappresentare un conto  bancario di risparmio, 

 Dovrà avere un tasso di interesse annuo determinato  al momento dell’apertura 

 Dovrà avere un metodo addInterest per accreditare  gli interessi sul conto

Un conto bancario di risparmio ha tutte le stesse  caratteristiche di un conto bancario, più alcune  altre caratteristiche che gli sono peculiari

 Allora possiamo riutilizzare il codice già scritto per la  classe BankAccount

10

La classe BankAccount

public class BankAccount { public BankAccount() { balance = 0;

}

public void deposit(double amount) { balance = balance + amount;

}

public void withdraw(double amount) { balance = balance - amount;

}

public double getBalance() { return balance;

}

private double balance;

}

La classe SavingsAccount

public class SavingsAccount

{ public SavingsAccount(double rate) { balance = 0;

interestRate = rate;

} public void addInterest()

{ deposit(getBalance() * interestRate / 100);

}

private double interestRate;

public void deposit(double amount) { balance = balance + amount;

} public void withdraw(double amount) { balance = balance - amount;

} public double getBalance() { return balance;

}

private double balance;

Riutilizzo del codice

Come avevamo previsto, buona parte del codice  scritto per BankAccount ha potuto essere copiato  nella definizione della classe SavingsAccount

Abbiamo poi fatto tre cambiamenti

 abbiamo aggiunto una variabile di esemplare

interestRate

 abbiamo modificato il costruttore

• ovviamente il costruttore ha anche cambiato nome

 abbiamo aggiunto un metodo

addInterest

(4)

13

Riutilizzo del codice

Copiare il codice è già un risparmio di tempo, però  non è sufficiente

Cosa succede se modifichiamo BankAccount?

 ad esempio, modifichiamo withdraw in modo da  impedire che il saldo diventi negativo

Dobbiamo modificare di conseguenza anche  SavingsAccount

 molto scomodo…

 fonte di errori…

L’ereditarietà risolve questi problemi

14

Ri­scriviamo la classe SavingsAccount usando il  meccanismo dell'ereditarietà

Dichiariamo che SavingsAccount è una classe  derivata da BankAccount (extends)

eredita tutte le caratteristiche di BankAccount

specifichiamo soltanto le peculiarità di SavingsAccount

SavingsAccount

public class SavingsAccount extends BankAccount { public SavingsAccount(double rate)

{ interestRate = rate;

} public void addInterest()

{ deposit(getBalance() * interestRate / 100);

} private double interestRate;

}

Come usare la classe derivata

Oggetti della classe derivata SavingsAccount si  usano come se fossero oggetti di BankAccount

 con qualche proprietà in più

La classe derivata si chiama sottoclasse

 La classe da cui si deriva si chiama superclasse

SavingsAccount acct = new SavingsAccount(10);

acct.deposit(500);

acct.withdraw(200);

acct.addInterest();

System.out.println(acct.getBalance());

330

Osservazioni su SavingsAccount

Il metodo addInterest usa i metodi getBalance e  deposit di BankAccount

Non specifica un parametro implicito

 Cioè il parametro implicito è this

Il metodo addInterest invoca getBalance e deposit  invece che leggere/scrivere direttamente il campo  balance

Questo perché il campo balance è definito come  private in BankAccount

Mentre addInterest è definito in SavingsAccount e  non ha accesso a balance

(5)

17

La superclasse universale Object

In Java, ogni classe che non deriva da nessun’altra  deriva implicitamente dalla superclasse 

universale del linguaggio, che si chiama Object

Quindi, SavingsAccount deriva da BankAccount,  che a sua volta deriva da Object

Object ha alcuni metodi, che vedremo più avanti (tra  cui toString), che quindi sono ereditati da tutte le  classi in Java

 l’ereditarietà avviene anche su più livelli, quindi 

SavingsAccount eredita anche le proprietà di Object

18

Ereditarietà

Sintassi:

Scopo: 

 definire la classe NomeSottoclasse che deriva dalla  classe NomeSuperclasse

 definendo nuovi metodi e/o nuove variabili, oltre ai  suoi costruttori

Nota: una classe estende sempre una sola altra  classe

Nota: se la superclasse non è indicata esplicitamente,  il compilatore usa implicitamente java.lang.Object

class NomeSottoclasse extends NomeSuperclasse { costruttori

nuovi metodi nuove variabili }

Terminologia e notazione

Confondere superclassi e sottoclassi

Dato che oggetti di tipo SavingsAccount sono

un’estensione di oggetti di tipo BankAccount 

 più “grandi” di oggetti di tipo BankAccount, nel senso  che hanno una variabile di esemplare in più

 più “abili” di oggetti di tipo BankAccount, perché  hanno un metodo in più

perché mai SavingsAccount si chiama sottoclasse  e non superclasse?

 è facile fare confusione

 verrebbe forse spontaneo usare i nomi al contrario...

(6)

21

Confondere superclassi e sottoclassi

I termini superclasse e sottoclasse derivano dalla  teoria degli insiemi

I conti bancari di risparmio (gli oggetti di tipo 

SavingsAccount) costituiscono un sottoinsieme  dell’insieme di tutti i conti bancari (gli oggetti di tipo  BankAccount) 

BankAccount SavingsAccount

22

Diagrammi e gerarchie di ereditarietà

Abbiamo già visto diagrammi di classi per  visualizzare accoppiamento tra classi

In un diagramma di classi l’ereditarietà  viene rappresentata mediante una freccia  con la punta a “triangolo vuoto”, diretta  verso la superclasse.

Abbiamo già visto diagrammi di  ereditarietà…

 La gerarchia delle classi di eccezioni

 Le gerarchie delle classi del pacchetto io

InputStream, OutputStream, Reader, Writer

Gerarchia delle  eccezioni

È tutto chiaro? …

1. Quali sono i campi di esemplare presenti in un  oggetto di tipo SavingsAccount?

2. Elencare quattro metodi che possono essere  invocati con un oggetto di tipo SavingsAccount

3. Se la classe Manager estende Employee, qual 

è la superclasse e quale la sottoclasse?

(7)

25

Metodi di una sottoclasse

26

Metodi di una sottoclasse

Quando definiamo una sottoclasse, possono verificarsi  tre diverse situazioni per quanto riguarda i suoi metodi

 nella sottoclasse viene definito un metodo che nella  superclasse non esisteva

Ad esempio il metodo addInterest di SavingsAccount

 un metodo della superclasse viene ereditato dalla  sottoclasse

Ad esempio i metodi deposit, withdraw, getBalance di  SavingsAccount, ereditati da BankAccount

 un metodo della superclasse viene sovrascritto nella  sottoclasse

Vediamo ora un esempio anche di questo caso

Sovrascrivere un metodo

La possibilità di sovrascrivere (override) un metodo  della superclasse, modificandone il comportamento  quando è usato per la sottoclasse, è una delle  caratteristiche più potenti di OOP

Per sovrascrivere un metodo bisogna definire nella  sottoclasse un metodo con la stessa firma di quello  definito nella superclasse

 tale metodo prevale su quello della superclasse  quando viene invocato con un oggetto della  sottoclasse

Sovrascrivere un metodo: esempio

Vogliamo modificare la classe SavingsAccount in  modo che ogni operazione di versamento abbia un  costo (fisso) FEE, che viene automaticamente  addebitato sul conto

 I versamenti nei conti di tipo SavingsAccount si  fanno però invocando il metodo deposit di  BankAccount, sul quale non abbiamo controllo

 Possiamo però sovrascrivere deposit, ridefinendolo  in SavingsAccount

In più aggiungiamo una costante di classe FEE, che  contenga l'importo del costo fisso da addebitare

(8)

29

Sovrascrivere un metodo: esempio

Sovrascriviamo il metodo deposit

Quando viene invocato deposit con un oggetto di tipo  SavingsAccount, viene invocato il metodo deposit  definito in SavingsAccount e non quello definito in  BankAccount

nulla cambia per oggetti di tipo BankAccount,  ovviamente

public class SavingsAccount extends BankAccount { ...

public void deposit(double amount) { withdraw(FEE);

... // aggiungi amount a balance } ...

private final static double FEE = 2.58; // euro }

30

Sovrascrivere un metodo: esempio

Proviamo a completare il metodo

 dobbiamo versare amount e sommarlo a balance

non possiamo modificare direttamente balance, che è  una variabile privata in BankAccount

 l’unico modo per aggiungere una somma di denaro a  balance è l’invocazione del metodo deposit

Così non funziona, perché il metodo diventa ricorsivo  con ricorsione infinita!!! (manca il caso base…)

public class SavingsAccount extends BankAccount { ...

public void deposit(double amount) { withdraw(FEE);

deposit(amount); // NON FUNZIONA }}

Sovrascrivere un metodo: Esempio

Ciò che dobbiamo fare è invocare il metodo  deposit di BankAccount

Questo si può fare usando il riferimento implicito  super, gestito automaticamente dal compilatore per  accedere agli elementi ereditati dalla superclasse

public class SavingsAccount extends BankAccount { ...

public void deposit(double amount) { withdraw(FEE);

super.deposit(amount); //invoca deposit della superclasse }}

Invocare un metodo della superclasse

Sintassi:

Scopo: invocare il metodo nomeMetodo della  superclasse anziché il metodo con lo stesso nome  (sovrascritto) della classe corrente

super è un riferimento all'oggetto parametro implicito  del metodo, come il riferimento this

super viene creato al momento dell'invocazione del  metodo, come il riferimento this

Però super tratta l'oggetto a cui si riferisce come se  fosse un esemplare della superclasse: questa è  l'unica differenza tra super e this

super.nomeMetodo(parametri)

(9)

33

Campi di esemplare di una  sottoclasse

34

Ereditare campi di esemplare

Quando definiamo una sottoclasse, possono 

verificarsi solo due diverse situazioni per quanto  riguarda i suoi campi di esemplare 

 nella sottoclasse viene definito un campo di  esemplare che nella superclasse non esisteva

Ad esempio il campo interestRate di SavingsAccount

 un campo della superclasse viene ereditato dalla  sottoclasse

Ad esempio il campo balance di SavingsAccount,  ereditato da BankAccount

Non è possibile sovrascrivere un campo della  superclasse nella sottoclasse

Ereditare campi di esemplare

Cosa succede se nella sottoclasse viene definito un  campo omonimo di uno della superclasse?

 È un’operazione lecita

 Ma è sconsigliabile

Un oggetto della sottoclasse avrà due campi di  esemplare con lo stesso nome ma in generale valori  diversi

 In particolare il nuovo campo di esemplare mette in  ombra il suo omonimo della superclasse

Ereditare campi di esemplare

In questo caso la compilazione ha successo

Ma viene aggiornata la variabile balance sbagliata!

Gli altri metodi, ereditati da BankAccount, faranno  riferimento alla variabile balance ereditata da  BankAccount

public class SavingsAccount extends BankAccount { ...

public void deposit(double amount) { withdraw(FEE);

balance = balance + amount;

} ...

private double balance; // Meglio non farlo!

}

(10)

37

Mettere in ombra campi ereditati

Perchè Java presenta questa apparente “debolezza”?

 Chi scrive una sottoclasse non deve conoscere niente di  quanto dichiarato private nella superclasse, né scrivere  codice che si basa su tali caratteristiche: incapsulamento!

 quindi è perfettamente lecito definire e usare una variabile  balance nella sottoclasse

sarebbe strano impedirlo: chi progetta la sottoclasse non sa  che esiste una variabile balance nella superclasse!

 Ma non si può usare nella sottoclasse variabili omonime di  quelle della superclasse e sperare che siano la stessa cosa 

38

Costruttori di una  sottoclasse

Costruttori di una sottoclasse

Che ne è delle altre variabili di esemplare?

In questo caso balance

Possiamo utilizzare i costruttori della superclasse per  inizializzarle?

public class SavingsAccount extends BankAccount { public SavingsAccount(double rate)

{

interestRate = rate;

} ...

}

Costruttori di una sottoclasse

Come per i metodi, possiamo usare il riferimento  super per riferirci ai costruttori della superclasse

La parola chiave super seguita da parentesi tonde  indica una chiamata al costruttore della superclasse

 Tale chiamata deve essere il primo enunciato del  costruttore della sottoclasse

public class SavingsAccount extends BankAccount { public SavingsAccount(

double initialBalance, double rate) { super(initialBalance);

interestRate = rate;

}...

}

(11)

41

Costruttori di una sottoclasse

E se non invochiamo un costruttore della  superclasse?

Allora viene invocato il costruttore predefinito della  superclasse (quello privo di parametri espliciti)

 Se questo non esiste (tutti i costruttori della  superclasse richiedono parametri espliciti) viene  generato un errore in compilazione

public class SavingsAccount extends BankAccount { public SavingsAccount(double rate)

{ //super(); invocato automaticamente interestRate = rate;

}...

}

42

È tutto chiaro? …

1. Perché il costruttore di SavingsAccount visto  prima non invoca il costruttore della 

superclasse?

2. Quando si invoca un metodo della superclasse  usando l’enunciato super, l’invocazione deve  essere il primo enunciato del metodo della  sottoclasse?

Esercizio: la classe  CheckingAccount

La classe CheckingAccount

public class CheckingAccount extends BankAccount { public CheckingAccount(double initialBalance)

{ super(initialBalance); // costruisci la superclasse transactionCount = 0; // azzera conteggio transaz.

}

public void deposit(double amount) //SOVRASCRITTO!!

{ transactionCount++;

super.deposit(amount); // aggiungi amount al saldo }

public void withdraw(double amount) //SOVRASCRITTO!!

{ transactionCount++;

super.withdraw(amount); // sottrai amount dal saldo } // continua

(12)

45

La classe CheckingAccount

// continua public void deductFees() //NUOVO METODO

{ if (transactionCount > FREE_TRANSACTIONS) {

double fees = TRANSACTION_FEE *

(transactionCount – FREE_TRANSACTIONS);

super.withdraw(fees);

}

transactionCount = 0;

}

//nuovi campi di esemplare private int transactionCount;

private static final int FREE_TRANSACTIONS = 3;

private static final double TRANSACTION_FEE = 2.0;

}

46

La superclasse BankAccount

public class BankAccount { public BankAccount() { balance = 0; }

public BankAccount(double initialBalance) { balance = initialBalance; }

public void deposit(double amount) { balance = balance + amount; } public void withdraw(double amount) { balance = balance - amount; } public double getBalance() { return balance; }

public void transfer(double amount, BankAccount other) { withdraw(amount);

other.deposit(amount);

} private double balance;

}

È tutto chiaro? …

1. Perché il metodo withdraw di CheckingAccount  invoca super.withdraw?

2. Perché il metodo deductFees pone a zero il 

conteggio delle transazioni effettuate? Conversioni di tipo tra 

superclasse e sottoclasse

(13)

49

Conversione fra riferimenti

Un oggetto di tipo SavingsAccount è un caso  speciale di oggetti di tipo BankAccount

Questa proprietà si riflette in una proprietà sintattica

 una variabile oggetto del tipo di una superclasse può  riferirsi ad un oggetto di una classe derivata 

non c’è nessuna “conversione” effettiva, cioè non  vengono modificati i dati

SavingsAccount collegeFund = new SavingsAccount(10); //tutto ok ...BankAccount anAccount = collegeFund; // questo è lecito!!

Object anObject = collegeFund; // anche questo!!

50

Conversione fra riferimenti

Le tre variabili, di tipi diversi, puntano ora allo  stesso oggetto

SavingsAccount collegeFund = new SavingsAccount(10); //tutto ok ...BankAccount anAccount = collegeFund; // questo è lecito!!

Object anObject = collegeFund; // anche questo!!

Conversione fra riferimenti

Tramite la variabile anAccount si può usare  l’oggetto come se fosse di tipo BankAccount, 

Però non si può accedere alle proprietà specifiche  di SavingsAccount

SavingsAccount collegeFund = new SavingsAccount(10);

BankAccount anAccount = collegeFund;

anAccount.deposit(10000); // questo è lecito perché

// deposit è un metodo di BankAccount

anAccount.addInterest(); // questo NON è lecito perché

//addInterest NON è metodo di BankAccount cannot resolve symbol

symbol : method addInterest() location: class BankAccount anAccount.addInterest();

^

public class BankAccount { ...

public void transferTo(BankAccount other, double amount) { withdraw(amount); // ovvero, this.withdraw(...) other.deposit(amount);

} ...

}

Conversione tra riferimenti

Aggiungiamo il metodo transferTo a BankAccount

Per quanto detto finora, il parametro other può anche  riferirsi ad un oggetto di tipo SavingsAccount

BankAccount acct1 = new BankAccount(500);

BankAccount acct2 = new BankAccount();

acct1.transferTo(acct2, 200);

System.out.println(acct1.getBalance());

System.out.println(acct2.getBalance()); 300 200

BankAccount other = new BankAccount(1000);

SavingsAccount sAcct = new SavingsAccount(10);

BankAccount bAcct = sAcct;

(14)

53

Conversione fra riferimenti

La conversione tra riferimento a sottoclasse e  riferimento a superclasse può avvenire anche  implicitamente (come tra int e double)

Il compilatore sa che il metodo transferTo richiede un  riferimento di tipo BankAccount, quindi

 controlla che sAcct sia un riferimento di tipo  BankAccount o di una sua sottoclasse 

 effettua la conversione automaticamente

BankAccount other = new BankAccount(1000);

SavingsAccount sAcct = new SavingsAccount(10);

other.transferTo(sAcct, 500);

54

Conversione fra riferimenti

Vediamo la conversione inversa di quella vista finora

 Ovvero la conversione di un riferimento a superclasse in  un riferimento a sottoclasse 

Questa non può avvenire automaticamente

Ma ha senso cercare di effettuarla?

 In generale non ha senso, perché in generale un oggetto  della superclasse non è un oggetto della sottoclasse

BankAccount bAcct = new BankAccount(1000);

SavingsAccount sAcct = bAcct;

incompatible types found : BankAccount required: SavingsAccount sAcct = bAcct;

^ 1 error

Conversione fra riferimenti

Tale conversione ha senso soltanto se, per le  specifiche dell’algoritmo, siamo sicuri che il  riferimento a superclasse punta in realtà ad un  oggetto della sottoclasse

Richiede un cast esplicito

 Richiede attenzione, perché se ci siamo sbagliati verrà  lanciata un’eccezione

SavingsAccount sAcct = new SavingsAccount(1000);

BankAccount bAcct = sAcct;

...SavingsAccount sAcct2 = (SavingsAccount) bAcct;

// se in fase di esecuzione bAcct non punta // effettivamente ad un oggetto SavingsAccount // l’interprete lancia ClassCastException

Polimorfismo ed ereditarietà

(15)

57

Polimorfismo

Sappiamo che un oggetto di una sottoclasse può essere  usato come se fosse un oggetto della superclasse

Ma quale metodo deposit viene invocato, quello di  BankAccount o quello ridefinito in SavingsAccount?

acct è una variabile dichiarata di tipo BankAccount

 Ma contiene un riferimento ad un oggetto che, in realtà, è  di tipo SavingsAccount!

 Questa informazione è disponibile solo in esecuzione  (all'interprete Java), non in compilazione

 secondo la semantica di Java, viene invocato il metodo  deposit di SavingsAccount

BankAccount acct = new SavingsAccount(10);

acct.deposit(500);

acct.withdraw(200);

58

Polimorfismo

In Java il tipo di una variabile non determina in modo  completo il tipo dell’oggetto a cui essa si riferisce

Questa semantica si chiama polimorfismo (“molte 

forme”) ed è caratteristica dei linguaggi Object­Oriented 

La stessa operazione (ad es. deposit) può essere svolta  in modi diversi, a seconda dell'oggetto a cui ci si riferisce

L’invocazione di un metodo è sempre determinata dal  tipo dell’oggetto effettivamente usato come parametro  implicito, e NON dal tipo della variabile oggetto

 La variabile oggetto ci dice cosa si può fare con  quell'oggetto (cioè quali metodi si possono utilizzare)

 L'oggetto ci dice come farlo

Polimorfismo

Abbiamo già visto il polimorfismo a proposito dei  metodi sovraccarichi

l’invocazione del metodo println si traduce in una  invocazione di un metodo scelto fra alcuni metodi con  firme diverse

il compilatore decide quale metodo invocare

In questo caso la situazione è molto diversa, perché  la decisione non può essere presa dal compilatore,  ma deve essere presa dall’ambiente runtime 

(l’interprete)

 Si parla di selezione posticipata (late binding)

 Mentre nel caso di metodi sovraccarichi si parla di  selezione anticipata (early binding) 

Controllo del tipo e instanceof

Se si vuole controllare il tipo dell'oggetto a cui una  variabile oggetto si riferisce, si può usare un nuovo  operatore: instanceof

 È un operatore relazionale (restituisce valori booleani)

Restituisce true se la variabile oggetto (primo  argormento sta puntando ad un oggetto del tipo  specificato dal secondo argomento

 Ad esempio

SavingsAccount collegeFund = new SavingsAccount(10);

BankAccount anAccount = collegeFund;

if (anAccount instanceof SavingsAccount)

SavingsAccount a = (SavingsAccount) anAccount;

Restituisce true

(16)

61

Controllo del tipo e instanceof 

Sintassi:

Restituisce 

true se varOggetto contiene un riferimento ad un oggetto  della classe NomeClasse (o una sua sottoclasse)

 false altrimenti

In caso di valore restituito true un eventuale cast di  varOggetto ad una variabile di tipo NomeClasse NON  lancia l’eccezione ClassCastException

Nota: il risultato non dipende dal tipo di varOggetto, ma dal  tipo dell’oggetto a cui essa si riferisce al momento 

dell’esecuzione

varOggetto instanceof NomeClasse

62

Esercizio: polimorfismo

La classe AccountTester (vers. 1)

public class AccountTester1

{ public static void main(String[] args)

{ SavingsAccount momsSavings = new SavingsAccount(0.5);

CheckingAccount harrysChecking = new CheckingAccount(100);

// metodi polimorfici di BankAccount e sue sottoclassi momsSavings.deposit(10000);

momsSavings.transfer(2000, harrysChecking);

harrysChecking.withdraw(1500);

harrysChecking.withdraw(80);

momsSavings.transfer(1000, harrysChecking);

harrysChecking.withdraw(400);

// simulazione della fine del mese

momsSavings.addInterest(); //metodo solo di SavingsAccount harrysChecking.deductFees();//metodo solo di CheckingAccount System.out.println("Mom’s savings balance = $"

+ momsSavings.getBalance());

System.out.println("Harry’s checking balance = $"

+ harrysChecking.getBalance());

}

La classe AccountTester (vers. 2)

public class AccountTester2

{ public static void main(String[] args)

{ BankAccount momsSavings = new SavingsAccount(0.5);

BankAccount harrysChecking = new CheckingAccount(100);

// metodi polimorfici di BankAccount e sue sottoclassi momsSavings.deposit(10000);//vengono invocati i metodi delle momsSavings.transfer(2000, harrysChecking); //sottoclassi harrysChecking.withdraw(1500); //anche se i riferimenti sono harrysChecking.withdraw(80); //di tipo BankAccount

momsSavings.transfer(1000, harrysChecking);

harrysChecking.withdraw(400);

// simulazione della fine del mese

((SavingsAccount)momsSavings).addInterest(); //e` necessario ((CheckingAccount)harrysChecking).deductFees();//fare i cast System.out.println("Mom’s savings balance = $"

+ momsSavings.getBalance());

System.out.println("Harry’s checking balance = $"

+ harrysChecking.getBalance());

}

(17)

65

Riflessione:

Object Oriented Programming Obiettivi e principi di design

66

OOP ­ obiettivi

Robustezza

 Vogliamo scrivere programmi capaci di gestire situazioni  inaspettate (ad es. input inattesi)

 Requisito particolarmente importante in applicazioni “life­

critical”

Adattabilità

 Vogliamo scrivere programmi capaci di evolvere (ad es. 

girare su diverse architetture o avere nuove funzionalità)

 Un concetto correlato è quello di portabilità

Riusabilità

 Vogliamo che il nostro codice sia utilizzabile come  componente di diversi sistemi in varie applicazioni

 Deve essere chiaro cosa il nostro codice fa, e cosa non fa

OOP ­ principi di design

Astrazione

 “Distillare” i concetti che meglio rappresentano un  oggetto o un sistema

Information hiding

 Nascondere l’informazione ad utenti esterni, lasciando  vedere solo l’interfaccia

 In questo modo parti di un programma possono cambiare  senza effetti sulle altre

Modularità

 Organizzare un sistema software in componenti  funzionali separate

Tutti questi principi di design

Sono supportati in un linguaggio di programmazione OO Aiutano ad ottenere robustezza, adattabilità, riusabilità

OOP ­ Paradigmi

Abbiamo esaminato i tre principali paradigmi di  programmazione che caratterizzano un linguaggio OO

Classi, oggetti, incapsulamento

L’informazione viene nascosta dentro scatole nere (le  classi), e l’accesso ad essa è controllato

Ereditarietà

Una classe può essere estesa da sottoclassi, che ne  ereditano le funzioni e le specializzano

Polimorfismo

Il tipo di una variabile non determina completamente il tipo  dell’oggetto a cui essa si riferisce

(18)

69

La superclasse universale  Object

70

La classe Object

Sappiamo già che ogni classe di Java è (in maniera  diretta o indiretta) sottoclasse di Object

 Quindi ogni classe di Java eredita tutti i metodi della  classe Object

Alcuni dei più utili metodi di Object sono i seguenti

Ma perché siano davvero utili, nella maggior parte  dei casi bisogna sovrascriverli.

Sovrascrivere il metodo  toString

Metodi “magici”

Il metodo println, è in grado di ricevere come  parametro un oggetto di qualsiasi tipo

 Ora siamo in grado di comprendere questa “magia”

 qualsiasi riferimento può sempre essere convertito  automaticamente in un riferimento di tipo Object

Ma un esemplare di String è anche di tipo Object…

 come viene scelto, tra i metodi sovraccarichi, quello  giusto quando si invoca println con una stringa?

 Il compilatore cerca sempre di “fare il minor numero  di conversioni”

 viene usato, quindi, il metodo “più specifico”

public void println(Object obj) //invocato per oggetti generici { println(obj.toString()); }

public void println(String s) // invocato per le stringhe { ... }

(19)

73

Il metodo toString

Passando un oggetto qualsiasi a System.out.println  si visualizza la sua descrizione testuale standard

println invoca toString dell’oggetto, e l’invocazione è  possibile perché tutte le classi hanno il metodo  toString, eventualmente ereditato da Object

Il metodo toString di una classe viene invocato  implicitamente anche per concatenare un oggetto  con una stringa:

La seconda riga viene interpretata dal compilatore  come se fosse stata scritta così: 

BankAccount a = new BankAccount(1000);

System.out.println(a);

BankAccount acct = new BankAccount();

String s = "Conto " + acct;

String s = "Conto " + acct.toString(); 74

Il metodo toString

Il metodo toString della classe Object ha la firma

L’invocazione di questo metodo per qualsiasi oggetto  ne restituisce la descrizione testuale standard

il nome della classe seguito dal carattere @ e dallo  hashcode dell’oggetto

Che è un numero univocamente determinato dall’indirizzo  in memoria dell’oggetto stesso

In generale la descrizione testuale standard non è  particolarmente utile

 È piu utile ottenere una stringa di testo contenente  informazioni sullo stato dell’oggetto in esame

public String toString()

BankAccount a = new BankAccount(1000);

System.out.println(a); BankAccount@111f71

Sovrascrivere il metodo toString

Sovrascrivere il metodo toString nelle classi che si  scrivono è considerato buono stile di programmazione

toString dovrebbe sempre produrre una stringa 

contenente tutte le informazioni di stato dell’oggetto

 Il valore di tutte le sue variabili di esemplare

 Il valore di variabili statiche non costanti della classe

Questo stile di programmazione è molto utile per il  debugging ed è usato nella libreria standard

 Esempio: descrizione testuale di BankAccount 

 Bisogna sovrascrivere toString in BankAccount 

System.out.println(anAccount);

BankAccount[balance=1500]

Esempio: toString in BankAccount

toString crea una stringa contenente il nome della  classe seguito dai valori dei campi di esemplare  racchiusi fra parentesi quadre.

public class BankAccount { ...

public String toString()

{ return "BankAccount[balance=" + balance + "]";

}}

(20)

77

Materiale di complemento (capitolo 10)

78

Metodo toString: lo stile adottato  nella libreria standard

Esempio: toString in BankAccount

Stile adottato nella libreria standard:

toString crea una stringa contenente il nome della  classe seguito dai valori dei campi di esemplare  racchiusi fra parentesi quadre.

Problema: devo sovrascrivere toString anche per le  sottoclassi, per vedere stampato il nome di classe  corretto

public class BankAccount { ...

public String toString()

{ return “BankAccount[balance=” + balance + “]”;

}}

Esempio: toString in BankAccount

È possibile evitare di scrivere esplicitamente il nome  della classe all’interno del metodo

Si può usare il metodo getClass, che restituisce un  oggetto di tipo classe

E poi invocare il metodo getName sull’oggetto di tipo  classe, per ottenere il nome della classe

toString visualizza il nome corretto della classe anche  quando viene invocato su un oggetto di una sottoclasse

public String toString()

{ return getClass().getName() +

“[balance=” + balance + “]”;

}

(21)

81

Esempio: toString in SavingsAccount

Ovviamente, se si vogliono visualizzare i valori di  nuovi campi di esemplare di una sottoclasse bisogna  sovrascrivere toString

Bisogna invocare super.toString per ottenere i valori  dei campi della superclasse

public class SavingsAccount extends BankAccount { ...

public String toString() { return super.toString() +

“[interestRate=” + interestRate + “]”;

} ...

}

82

Controllo di accesso

Controllo di accesso

Java fornisce quattro livelli per il controllo di  accesso a metodi, campi, e classi

public

private

protected

package

Finora noi abbiamo sempre usato solo i primi due

 Ora siamo in grado di comprendere anche gli altri due

 In particolare lo specificatore di accesso protected

Accesso package

Un membro di classe (o una classe) per il quale non  viene specificato un identificatore di accesso ha  un’impostazione di accesso predefinita

 Accesso di pacchetto

Tutti i metodi delle classi contenute nello stesso  pacchetto vi hanno accesso

 Può essere una buona impostazione per le classi

Non lo è per le variabili, perché si viola  l’incapsulamento dell’informazione 

(22)

85

Accesso package

Errore comune: dimenticare lo specificatore di  accesso per una variabile di esemplare

Questo è un rischio per la sicurezza

 Un altro programmatore può realizzare una nuova classe  nello stesso pacchetto e ottenere l’accesso al campo di  esemplare

È meglio usare (quasi) sempre campi di esemplare  private

public class Window extends Container { String warningString;

...

}

86

Accesso protected

Abbiamo visto che una sottoclasse non può accedere  a campi private ereditati dalla propria superclasse

 Deve usare i metodi (public) appropriati della  superclasse

Esempio: il metodo addInterest di SavingsAccount

public class SavingsAccount extends BankAccount { ...

public void addInterest()

{ deposit(getBalance() * interestRate / 100);

} ...

}

Accesso protected

Il progettista della superclasse può rendere accessibile  in modo protected un campo di esemplare

Tutte le sottoclassi vi hanno accesso

Attenzione: è una violazione dell’incapsulamento

 Il progettista della superclasse non ha alcun controllo  sui progettisti di eventuali sottoclassi

 Diventa difficile modificare la superclasse: non si  possono modificare variabili protected perché qualcuno  potrebbe aver scritto una sottoclasse che ne fa uso

Anche i metodi possono essere definiti protected

 possono essere invocati solo nella classe in cui sono  definiti e nelle classi da essa derivate

public class BankAccount { ...

protected double balance;

}

Il modificatore di accesso 

final

(23)

89

Classi e metodi final

Abbiamo già visto che la parola chiave final può  essere usata nella definizione di una variabile per  impedire successive assegnazioni di valori

Anche un metodo di una classe può essere  dichiarato come final

Un metodo di una classe dichiarato final non può  essere sovrascritto dalle sottoclassi

Anche una intera classe può essere dichiarata  come final

Una classe dichiarata final non può essere estesa

90

Classi e metodi final

Esempio: la classe String è una classe immutabile

Gli oggetti di tipo String non possono essere  modificati da nessuno dei loro metodi

La classe String nella libreria standard è dichiarata  in questo modo:

Nessuno può creare sottoclassi di String

 Quindi siamo sicuri che riferimenti ad oggetti di tipo  String si possono sempre copiare senza rischi di  cambiamenti

public final class String { ...

}

Classi e metodi final

In alcune circostanze può essere utile dichiarare un  metodo come final

Esempio: un metodo per verificare la correttezza di  una chiave

In questo modo nessuno può sovrascriverlo con un  metodo che restituisca sempre true

public class SecureAccount extends BankAccount { ...

public final boolean checkPassword(String password) { ...

}}

È tutto chiaro? …

1. Quale è una comune motivazione che porta a  definire campi di esemplare con accesso di  pacchetto?

2. Se una classe dotata di costruttore pubblico ha 

accesso di pacchetto, chi ne può costruire 

esemplari?

(24)

93 94

Interfacce (capitolo 9) e l’interfaccia Comparable 

(sezione 13.8)

Realizzare una proprietà astratta

Torniamo al problema enunciato qualche giorno fa: 

ordinare oggetti generici

 Affinché i suoi esemplari possano essere ordinati, una  classe deve definire un metodo adatto a confrontare  esemplari della classe, con lo stesso comportamento di  compareTo

Gli oggetti della classe diventano confrontabili

 gli algoritmi di ordinamento/ricerca non hanno bisogno  di conoscere alcun particolare del criterio di confronto  tra gli oggetti

 basta che gli oggetti siano tra loro confrontabili

Scrivere una interfaccia

Sintassi:

La definizione di una interfaccia in Java serve per  definire un comportamento astratto 

 Una interfaccia deve poi essere realizzata (implementata)  da una classe che dichiari di avere tale comportamento

Definire un’interfaccia è simile a definire una classe

 si usa la parola chiave interface al posto di class

Un’interfaccia può essere vista come una classe ridotta

non può avere costruttori

non può avere variabili di esemplare

contiene le firme di uno o più metodi non statici (definiti  implicitamente public), ma non può definirne il codice

public interface NomeInterfaccia { firme di metodi

}

(25)

97

Implementare una interfaccia

Esempio:

L'interfaccia Comparable

Se una classe intende realizzare concretamente un  comportamento astratto definito in un’interfaccia, deve

 Dichiarare di implementare l’interfaccia

Definire i metodi dell’interfaccia, con la stessa firma

public class BankAccount implements Comparable { ...

public int compareTo(Object other)

{ //other è un Object perché questa è la firma in //Comparable. Però facciamo un cast a BankAccount...

BankAccount acct = (BankAccount) other;

if (balance < acct.balance) return -1;

if (balance > acct.balance) return 1;

return 0;

} ...

}

public interface Comparable { int compareTo(Object other);

}

98

Conversione di tipo tra  classi e interfacce

Usare una interfaccia

In generale, valgono le regole di conversione viste  nell'ambito dell'ereditarietà

 Una interfaccia ha lo stesso ruolo di una superclasse

 Una classe che implementa un'interfaccia ha lo stesso  ruolo di una sottoclasse che estende la superclasse

È lecito definire una variabile il cui tipo è un'interfaccia

Ma non è possibile costruire oggetti da un’interfaccia

 Le interfacce non hanno campi di esemplare nè costruttori

 Allora a che serve avere una variabile oggetto il cui tipo è  quello di una interfaccia?

new Comparable(); // ERRORE IN COMPILAZIONE Comparable c; // corretto

Conversione da classe ad interfaccia

Una variabile oggetto di tipo Comparable può puntare  ad un oggetto di una classe che realizza Comparable

Usando la variabile c non sappiamo esattamente  quale è il tipo dell'oggetto a cui essa si riferisce

Non possiamo utilizzare tutti i metodi di BankAccount

Però siamo sicuri che quell'oggetto ha un metodo  compareTo

Comparable c = new BankAccount(10); //corretto

double saldo = c.getBalance(); //ERRORE in compilazione

Comparable d = new BankAccount(20);

(26)

101

Conversione da interfaccia a classe

A volte può essere necessario convertire un riferimento il  cui tipo è un interfaccia in un riferimento il cui tipo è una  classe che implementa l'interfaccia

Se siamo certi che c punta ad un oggetto di tipo 

BankAccount possiamo creare una nuova variabile acct  di tipo BankAccount e fare un cast di c su acct 

E se ci sbagliamo? ClassCastException in esecuzione

Comparable c = new BankAccount(10);

double saldo = c.getBalance(); //ERRORE! Come faccio?

Comparable c = new BankAccount(10);

BankAccount acct = (BankAccount) c;

double saldo = anAccount.getBalance(); //corretto

102

Polimorfismo e interfacce

Polimorfismo e interfacce

Possiamo invocare il metodo compareTo perché x è di  tipo Comparable

 Ma quale metodo compareTo?

Quello scritto in BankAccount o quello di String?

Il compilatore non ha questa informazione

Il tipo di oggetto a cui x si riferisce dipende dal flusso di  esecuzione

 In fase di esecuzione la JVM determina il tipo di oggetto 

Comparable x;

...if (...) x = new BankAccount(1000);

else x = new String(“”);

...if (x.compareTo(y) > 0) ...

Polimorfismo e interfacce

Il tipo di una variabile non determina in modo  completo il tipo dell’oggetto a cui essa si riferisce

Come per l'ereditarietà, questa semantica si chiama  polimorfismo 

La stessa operazione (compareTo) può essere svolta in  modi diversi, a seconda dell'oggetto a cui ci si riferisce

L’invocazione di un metodo è sempre determinata dal  tipo dell’oggetto effettivamente usato come parametro  implicito, e NON dal tipo della variabile oggetto

 La variabile oggetto (interfaccia) ci dice cosa si può fare  con quell'oggetto (cioè quali metodi si possono utilizzare)

 L'oggetto (appartenente ad una classe) ci dice come farlo

(27)

105

Polimorfismo e interfacce

Se la condizione dell'if è vera, x contiene un  riferimento ad un oggetto che, in quel momento  dell'esecuzione è di tipo BankAccount!

E l’interprete Java lo sa (il compilatore no)

 secondo la semantica di Java, viene invocato il  metodo compareTo scritto in BankAccount

Comparable x;

...if (...) x = new BankAccount(1000);

else x = new String(“”);

...if (x.compareTo(y) > 0) ...

106

Ordinamento di oggetti e  l'interfaccia Comparable

L’interfaccia Comparable

L’interfaccia Comparable è definita nel pacchetto  java.lang, per cui non deve essere importata né  deve essere definita

la classe String, ad esempio, implementa Comparable

Come può Comparable risolvere il nostro problema?

 Ovvero definire un metodo di ordinamento valido per  tutte le classi?

Basta definire un metodo per ordinare un array di  riferimenti ad oggetti che realizzano Comparable,  indipendentemente dal tipo

public interface Comparable { int compareTo(Object other);}

Ordinamento di oggetti

Tutti i metodi di ordinamento e ricerca che abbiamo  visto per array di numeri interi possono essere  riscritti per array di oggetti Comparable

 Basta usare le seguenti “traduzioni”

if (a < b) if (a > b) if (a == b) if (a != b)

if (a.compareTo(b) < 0) if (a.compareTo(b) > 0) if (a.compareTo(b) == 0) if (a.compareTo(b) != 0)

(28)

109

SelectionSort per oggetti Comparable

public class ArrayAlgs { ...

public static void selectionSort(Comparable[] v, int vSize) { for (int i = 0; i < vSize - 1; i++)

{ int minPos = findMinPos(v, i, vSize-1);

if (minPos != i) swap(v, minPos, i); } } private static void swap(Comparable[] v, int i, int j) { Comparable temp = v[i];

v[i] = v[j];

v[j] = temp;

} private static int findMinPos(Comparable[] v, int from, int to) { int pos = from;

Comparable min = v[from];

for (int i = from + 1; i <= to; i++) if (v[i].compareTo(min) < 0) { pos = i;

min = v[i]; } return pos;

} ...

} 110

Ordinamento di oggetti

Definito un algoritmo per ordinare un array di  riferimenti Comparable, se vogliamo ordinare un  array di oggetti BankAccount basta fare

Dato che BankAccount realizza Comparable, l’array di riferimenti viene convertito 

automaticamente

BankAccount[] v = new BankAccount[10];

...// creazione dei singoli elementi dell’array // ed eventuali modifiche allo stato

// degli oggetti dell’array ...ArrayAlgs.selectionSort(v);

Scrivere metodi compareTo

Il metodo compareTo

Il metodo compareTo deve definire una relazione di  ordine totale, ovvero deve avere queste proprietà

Antisimmetrica: a.compareTo(b) ≤ 0  b.compareTo(a) ≥ 0

Riflessiva: a.compareTo(a) = 0

Transitiva: a.compareTo(b) ≤ 0 e b.compareTo(c) ≤ 0

Implica a.compareTo(c)<=0 

a.compareTo(b) può restituire qualsiasi valore  negativo per segnalare che a precede b

if (a.compareTo(b) == -1) //errore logico!

if (a.compareTo(b) < 0) // giusto!

(29)

113

I metodi compareTo e equals

Una classe che implementa Comparable ha i metodi

compareTo (per implementare Comparable)

equals (ereditato da Object)

Il metodo compareTo definito in una classe dovrebbe  sempre essere consistente con il metodo equals:

(e1.compareTo((Object)e2) == 0) 

• dovrebbe sempre avere lo stesso valore booleano di

 e1.equals((Object)e2) per ogni e1 e e2 della classe

Il metodo equals in Object è:

Quindi una classe che implementa Comparable  dovrebbe sempre sovrascrivere equals…

 Non sempre noi lo faremo

public boolean equals(Object obj) { return (this == obj); }

114

Comparable e Java >= 5.0

In seguito all’introduzione in Java 5.0 dei tipi generici  (che non vediamo), l’utilizzo di Comparable induce il  compilatore all’emissione di strane (ma innocue) 

“segnalazioni”, che possono essere ignorate

Compilando con l’opzione ­nowarn si elimina  l’emissione delle segnalazioni

Note: MyClass.java uses unchecked or unsafe operations.

Note: Recompile with -Xlint:unchecked for details.

javac –nowarn MyClass.java

Riassunto: la classe ArrayAlgs

Ecco la classe ArrayAlgorithms con tutti i metodi  statici da noi sviluppati

 Tutti i metodi per l’ordinamento e ricerca agiscono su  array di tipo Comparable[ ] ed usano il metodo  compareTo per effettuare confronti tra oggetti

binarySearch potrebbe usare il metodo equals per  verificare l’uguaglianza

 Ma per avere comportamenti consistenti le classi che  implementano Comparable devono sovrascrivere equals

Per ordinare (o cercare valori in) array numerici 

possiamo usare classi involucro (Integer, Double,…)

Compilando questa classe vengono generati alcuni  messaggi di warning, che ignoreremo

La classe ArrayAlgs

public class ArrayAlgs

{// riportiamo solo i metodi per array di oggetti Comparable // i metodi per array di interi sono obsoleti

//--- selectionSort per oggetti Comparable --- public static void selectionSort(Comparable[] v, int vSize) { for (int i = 0; i < vSize - 1; i++)

{ int minPos = findMinPos(v, i, vSize-1);

if (minPos != i) swap(v, minPos, i); } } private static void swap(Comparable[] v, int i, int j) { Comparable temp = v[i];

v[i] = v[j];

v[j] = temp;

} private static int findMinPos(Comparable[] v, int from, int to) { int pos = from;

Comparable min = v[from];

for (int i = from + 1; i <= to; i++) if (v[i].compareTo(min) < 0) { pos = i;

min = v[i]; } return pos;

Riferimenti

Documenti correlati

[r]

- il compito e' stato rinormalizzato a 36, tenuto conto del notevole numero di domande rispetto al

[r]

Ci sono dei cambiamenti causati dall' uomo che, con il suo intervento trasforma gli oggetti e gli ambienti.. Quasi tutte le cose

− se un metodo ha questa funzione di costo asintotico si dice che ha complessità costante complessità costante. • log N log N - logaritmo in

- :l (A.T.R.) Alluvioni sciolte di rocce palcozoichc delle sponde occidcutu li della rossa tcuonica, sopra banchi sino a 40150 metri di spessore di argille plastiche bianche o grigie

[r]

È quanto afferma all'Adnkronos Salute il presidente nazionale dell'Andi (Associazione nazionale dentisti italiani), Gianfranco Prada, che commenta così i dati