Astrazione sui Dati

SPECIFICA DI ASTRAZIONI SUI DATI
1
Abstraction by specification sui dati
Un nuovo tipo di dato per cui siano stati specificati valori
e operazioni possibili ( tipo di dato astratto , ADT,
abstract data type) è anche detto data abstraction o
semplicemente tipo.
Astraendo dai dettagli di rappresentazione dei valori e
d implementazione delle operazioni, il resto del
programma dipende solo dalla specifica del tipo!
favorisce lo sviluppo indipendente di diverse parti del programma
limita l impatto di eventuali delle modifiche (che rimangono
localizzate all implementazione del tipo a patto che non cambi la
specifica)
permette di rimandare i dettagli implementativi e di
rappresentazione a fasi più avanzate dei progetti
2
1
Specifica di interfaccia di un ADT
La signature del metodo (o il costrutto interface)
definiscono solo la sintassi dei metodi
Per la semantica, non basta specificare i metodi come
procedural abstractions, perchè questi agiscono sulle
variabili di stato
2
Abstraction by Specification e ADT in
Java
ADT divide specifica ( che cosa ) da implementazione
( come )
Le classi sono l equivalente degli ADT nei linguaggi OO
Tuttavia la separazione fra specifica e implementazione
introdotta dalle classi Java non è sempre sufficiente
non vi sono modi espliciti per dividere specifica e implementazione
Come specificare il tipo? È sufficiente specificare i metodi con le
procedural abstractions? Ma questi agiscono sulle variabili di stato...
anche dichiarando le variabili di stato come private, in Java si
possono definire metodi pubblici che consentono agli utilizzatori
della classe di costruire codice dipendente dall implementazione
( esporre il rep )
I meccanismi di Java, assieme a JML, tuttavia consentono,
con una certa disciplina, di definire convenientemente delle
astrazioni sui dati.
4
2
JML per specificare classi: visibilità e
metodi puri
Nella specifica JML di un metodo pubblico (non statico)
possono comparire solo gli elementi pubblici del
metodo e della classe
in particolare, i parametri formali e \result, ma anche metodi
pubblici puri o attributi pubblici (ma NON i metodi pubblici che
non sono dichiarati pure)
Metodi puri: metodi (non statici) dichiarati con la keyword
JML pure . Sono metodi che non hanno effetti
collaterali.
vale assignable \nothing ma preferiamo scrivere pure perche ci
consente di richiamare il metodo in JML
anche i costruttori possono essere dichiarati pure . Significa che
possono inizializzare gli attributi dichiarati nella classe, ma non
possono modificare nient altro.
un metodo puro può solo chiamare altri metodi puri.
5
Esempio di metodo puro
Un metodo size() che restituisce il numero di elementi di
un contenitore è puro
//@ ensures (*\result è cardinalità di this *)
public int /*@ pure /*@ size(){}
4
3
Specifica di astrazioni sui dati
Esempio di specifica di un tipo di
dato: IntSet
Tipo: Un contenitore insieme di numeri interi (IntSet)
Valori possibili:
tutti gli insiemi finiti di numeri interi (di cardinalità qualsiasi)
(NB: un insieme non contiene elementi ripetuti)
esempi: , {1,10, 35}, {7}, {50, -1, 48, 18, 33, 6, 15, 1000}
Operazioni possibili su un insieme I
costruzione: crea I e lo inizializza come insieme vuoto
void insert (int x): aggiunge x agli elementi di I
void remove (int x): elimina x da I
boolean isIn (int x): dice se x appartiene a I o meno
int size( ): restituisce la cardinalità di I
int choose( ): restituisce un qualsiasi elemento di I
8
4
Specifica: osservatori
public class IntSet {
// OVERVIEW: insiemi di interi illimitati e modificabili;
//per es.: {1, 2, 10, -55}
observers sono
//observers:
metodi puri:
restituiscono solo
//@ ensures (*\result == true*) <==>
//@
(*x è fra gli elementi di this*);
informazioni sullo stato
public /*@ pure @*/ boolean isIn (int x){}
//@ ensures (*\result è cardinalità di this *);
public /*@ pure @*/ int size(){}
}
//@ ensures this.isIn(\result) && this.size()>0;
//@ signals (EmptyException) this.size()==0;
public /*@ pure @*/ int choose() throws EmptyException {
}
Spesso, definiamo gli observer con un commento (in
quanto difficili o impossibili da definire in termini di altri
operatori)
9
Specifica: costruttori e mutators
//costruttori:
//@ensures this.size()==0;
public IntSet(){// inizializza this come insieme vuoto
}
metodi che modificano
l oggetto
//mutators:
(mutators)
La loro specifica può
richiamere i metodi (puri)
//@ ensures this.isIn(x) &&
//@
(\forall int y; x!=y; \old(isIn(y)) <==> isIn(y)); observers
public void insert(int x){ //inserisce x in this
}
//@ ensures !this.isIn(x) &&
//@
(\forall int y; x!=y; \old(isIn(y)) <==> isIn(y));
public void remove(int x){//rimuove x da this
}
10
5
Osservazioni su IntSet
La specifica è basata su conoscenza comune degli insiemi.
Tutti conoscono insiemi, ma con altre astrazioni può non essere così
semplice fornire specifica comprensibile da tutti
La specifica può essere compilata (se si aggiungono opportuni
return), ma non esegue nulla (i body sono vuoti)
utile per type checking
i body saranno riempiti solo successivamente, quando si fa
implementazione
La specifica descrive quando lanciare eccezioni
a livello di specifica, perché chiamante deve sapere se eccezioni sono
lanciate, quali e quando
choose lancia eccezione perché ci si aspetta che ritorni intero: se non ci
sono elementi, che intero ritorniamo?
remove,insert, isIn, e size: non lanciano eccezioni: sono sempre
definite anche quando l ins. vuoto e elemento è già/non è in insieme.
NB: per insert e remove la scelta non è ovvia: potrebbe essere utile
lanciare eccezione se remove di elemento non in insieme, o insert di
11
Uso degli ADT
La specifica deve essere sufficiente per potere utilizzare
l astrazione senza conoscere l implementazione
Esempio:
//@ ensures (*restituisce IntSet con tutti e soli gli
//@ elementi di array a*);
public static IntSet getArray (int[] a) throws NullPointerException {}
La funzione può essere facilmente implementata usando
solo i metodi pubblici di IntSet:
IntSet s = new IntSet();// da specifica, s diventa insieme vuoto
for (int i=0; i<a.length; i++)
s.insert(a[i]);// da specifica, in s è inserito a[i]
return s;
12
6
Esempio di specifica di un tipo di
dato: Poly
Tipo: Polinomi a coefficienti interi (Poly)
Valori possibili:
esempio: 1 + 2x + 3x3 2x4
Costruzione: come costruire un polinomio? Conviene partire dal
polinomio più semplice: il monomio
Monomio ha coeff intero e grado >=0
Polinomi si ottengono poi combinando monomi con + - *
costruzione: crea monomio di grado e coefficiente dati. Come caso
particolare, il polinomio zero (grado 0 coeff 0)
public int degree() : restituisce grado del polinomio
public int coeff(int d) restituisce coeff. del termine di grado d
public Poly add(Poly q) restuisce somma di this e q
public Poly sub(Poly q) restuisce differenza di this e q
public Poly mul(Poly q) restuisce prodotto di this e q
13
Il tipo Poly: Specifica
prima parte
/*@ pure @*/ public class Poly {
// OVERVIEW: polinomi immutabili, con coefficienti interi
Classe pura: tutti i suoi
metodi e costruttori sono
puri
//observers:
//@ ensures (*\result è grado del polinomio *);
public int degree(){}
//@ requires d>=0;
//@ ensures (d>degree() ? \result ==0 :
//@ (*\result è coeff. del termine di grado d *);
public int coeff(int d){}
costruzione non è definita
per tutti i monomi: grado
negativo non è accettabile:
si lancia eccezione
(unchecked)
NegativeExponentException
//costruttori
//@ ensures this.degree()==0&& this.coeff(0)==0;
public Poly(){}//costruisce polinomio zero
//monomio di grado n e coeff c:
//@ ensures n>=0 && this.coeff(n) == c && this.degree() == n &&
//@
(\forall int i; 0<=i && i< this.degree(); this.coeff(i) ==0);
//@ signals(NegativeExponentException e) n<0;
public Poly(int c, int n) throws NegativeExponentException{}
14
7
Specifica del tipo Poly: produttori
I costruttori di Poly sono in grado di costruire solo monomi! Come costruire polinomi
più complessi? Con i produttori, che generano un monomio e lo modificano.
//@ ensures q != null && (* \result == this + q *);
//@signals (NullPointerException e) q == null;
//@ ensures q != null && (* \result == this - q *);
//@signals (NullPointerException e) q == null;
//@ ensures q != null && (* \result == this * q *);
//@signals (NullPointerException e) q == null;
//@ ensures (* \result == -this *);
Eccezioni: operazioni sono definite per tutti i Poly, ma non funzionano se passato
argomento null:
Si specifica eccezione (unchecked) NullPointerException
invece esponente negativo non può capitare per costruzione)
15
Uso della classe Poly: esempio
Classe Poly, se implementata, può essere usata senza conoscerne
l implementazione ma solo la specifica.
Es Scrivere programma che legge un Poly da input
Per costruire un Poly, dati i coefficienti, si sommano i monomi di cui e
composto il Poly
public static void main (String argv[]) throws java.io.IOException,
NegativeExponentException {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
System.out.println("Inserisci grado:");
Poly p = new Poly(); //polinomio p inizializzato a zero
int g = Integer.parseInt(in.readLine());
for (int i=0; i<=g; i++) {
System.out.println("Inserisci coeff. termine grado: +i + = );
int c = Integer.parseInt(in.readLine());
p = p.add(new Poly(c,i);); //p diventa p +cx^i
}
.... qui si puo usare il polinomio p.....
}
16
8
Oggetto Astratto vs. Oggetto Concreto
La specifica di un astrazione descrive un «oggetto astratto».
Es. per un IntSet
l oggetto astratto è «un insieme di interi»
L oggetto concreto potrebbe essere un ArrayList, un albero binario, o
altra struttura dati usata per l implementazione della classe IntSet
L implementazione di un astrazione descrive un «oggetto concreto»
Es. per Poly:
l
L oggetto concreto potrebbe essere un array o una lista delle adiacenze
o un altra struttura dati comoda.
I due concetti sono differenti e non vanno confusi
Le eventuali modifiche al codice potrebbero modificare, per
esigenze implementative, la struttura dati dell oggetto concreto,
lasciando immutato l oggetto astratto.
17
Specifica formale di collezioni
Oggetto Astratto Tipico
18
9
In generale lo stato concreto degli oggetti della classe
non è visibile (se attributi sono privati): non può (e non
deve) essere usato nella specifica
Lo stato astratto invece è osservabile attraverso metodi
observer (puri) e quindi usabile nella specifica
Però spesso i metodi observer non sono sufficienti a
descrivere pre/post/invariante, perché non catturano
completamente lo stato astratto.
Questo accade spesso (non sempre!) con le collezioni
19
19
Es: specifica di push e pop per Pila
public class Pila<T> {
//@ensures (*\result è numero di elementi in this *);
public int size(){..}
//@ requires size()>0;
//@ ensures (* \result è cima della pila *)
public /*@ pure @*/ T top();
//@ ensures size()==0;
public Pila() {..}
//@ensures size()==\old(size()+1)&&top()==x && (*elementi sotto il top invariati*)
public void push
//@ requires 0 < size();
//@ ensures size() == \old(size()-1) && (* elimina elemento da cima della pila *)
public void pop(){..}
}
20
10
L oggetto astratto tipico
La specifica della Pila è comprensibile, ma non è
completamente formalizzata.
Gli osservatori non bastano per definire il resto della specifica
Per formalizzarla, si può definire nella sola specifica un
oggetto astratto tipico (OAT), che definisce in modo preciso
l oggetto astratto definito dalla specifica della classe
L OAT è di solito una List o un set o una stringa
Le operazioni possono essere descritte come effetto
sull OAT
L OAT può essere utilizzato per specificare, in modo più o
meno formale, qualunque classe.
NB: JML fornisce una soluzione simile con i model (che non
vediamo). I model sono una formalizzazione dell OAT.
21
21
Esempio
Pila (LIFO) di interi
oggetto tipico: una sequenza di interi x1x2...xn
l operazione top() a partire da x1x2...xn restituisce xn
l operazione size() a partire da x1x2...xn restituisce n
l operazione push(k) a partire da x1x2...xn trasforma lo stack
in x1x2...xn k: ossia
+x
l operazione pop a partire da x1x2...xn genera x1x2...xn-1
Si può introdurre esplicitamente nei commenti un attributo oat ,
utilizzabile solo per la specifica ( spec-public )
22
22
11
Esempio: specifica con oggetto tipico
public class Pila<T> {
//@spec_public List<T> oat;
//@ Oggetto tipico
x_n>, n>=0, x_i in T, x_n è il top
//@ensures \result == oat.size();
public /*@ pure @*/ int size(){ .. }
//@requires size()>0;
//@ ensures \result== oat.get(oat.size()-1);
public /*@ pure @*/ T top() throw EmptyException;
//@ ensures size()==0;
public Pila() { .. }
//@ensures size()==\old(size()+1)&&top()==x &&
//@
\old(oat).equals(oat.sublist(0,oat.size()-2));
public void push
//@requires size()>0;
//@ensures size()==\old(size()-1)&& oat.equals(\old(oat.sublist(0,oat.size()-2))
public void pop
}
23
Es: Oggetto tipico per IntSet
public class IntSet {
//@spec_public List<Integer> oat;
//@ ensures \result == oat.contains(x) ;
public /*@ pure @*/ boolean isIn (int x){}
//@ ensures \result ==oat.size();
public /*@ pure @*/ int size(){}
}
24
12
Proprietà delle Data Abstractions
Categorie di operazioni
Creatori (creators): producono da zero nuovi oggetti.
Sono tutti dei costruttori
ma non tutti i costruttori sono creatori (quando hanno oggetti del proprio
tipo come argomenti sono invece produttori)
di solito sono puri
Produttori (producers): dati in input oggetti del proprio tipo creano
oggetti del proprio tipo
es. add in Poly: p.add(q) restituisce il Poly dato da this+q
di solito sono puri
Modificatori (mutators): modificano oggetti del proprio tipo
insert e remove in IntSet.
non hanno dichiarazione assignable in JML (e non sono mai puri)
Osservatori (observers): dati in input oggetti del proprio tipo
restituiscono risultati di altri tipi
size, choose e isIn per IntSet
tipicamente sono dichiarati come puri in JML
a volte combinati con produttori o modificatori: es. chooseAndRemove
27
13
Adeguatezza di un tipo (1)
Un tipo è adeguato se fornisce operazioni sufficienti
perché il tipo possa essere utilizzato semplicemente ed
efficientemente.
Es: se IntSet ha solo insert e remove, non si può verificare
membership.
Semplice verifica di adeguatezza:
Se tipo è mutable almeno: creators, observers e mutators
Se tipo è immutable almeno: creators, observers e producers
Tipo deve essere totalmente popolato (fully populated): usando
creators, producers e mutators deve essere possibile ottenere
(convenientemente) ogni possibile stato astratto
Ad esempio con solo i costruttori di Poly non si potrebbero
defininire tutti i polinomi a coeff. interi, ma usando i produttori
si . Tecnicamente, basterebbe add.
28
Adeguatezza di un tipo (2)
Anche se totalmente popolato, verificare se si può
ragionevolmente migliorare efficienza fornendo ulteriori
operazioni
Es. se solo IntSet(), insert, remove e size si può verificare
membership (verificando se size cambia dopo un remove) ma
sembra molto inefficiente ...
...ma non bisogna includere troppe operazioni (che non si
adattano allo scopo del tipo), altrimenti astrazione diventa più
difficile da capire, implementare e mantenere.
(es. calcolo della trasformata di Laplace o della derivata di un
Poly potrebbero essere lasciate a metodi statici esterni)
Adeguatezza è concetto informale e dipendente dal
contesto d uso. Se contesto è limitato, bastano poche
operazioni.
29
14
Proprietà astratte
Proprietà astratte: sono proprietà osservabili, con i
metodi pubblici observer
Si usa solo specifica dei metodi, non implementazione
Due categorie:
proprietà evolutiva: relazione fra uno stato astratto
osservabile e lo stato astratto osservabile
successivo
Es. Mutabilità; grado di un Poly non cambia
Non immediatamente rappresentabili in JML
proprietà invariante: proprietà degli stati astratti
osservabili.
es: size() di un IntSet è sempre >= 0. Grado di un Poly non è
negativo.
Sono rappresentabili in JML con public invariant
30
Esempi di Proprieta invarianti
31
15
Invarianti astratti
Ignoriamo implementazione e usiamo solo specifica
per dimostrare invarianti astratti (abstract invariant),
che sono formalizzazione di una proprietà astratta
Un abstract invariant è una condizione che deve
essere sempre verificata per l oggetto astratto
la dimensione di un IntSet è sempre >=0.
un coeff. di grado n di un Poly e sempre 0 per n>degree()
In JML si chiamano public invariant. Possono usare
solo attributi e metodi public della classe
//@ public invariant this.size()>=0
//@ public invariant this.degree()>=0;
32
Esempio
public /*@ pure @*/ class Triangolo {
//* Overview: un triangolo, individuato dai suoi vertici
//@ensures (* costruisce un triangolo non degenere *)
//@signals (* TriangoloInvalidoException e) vertici.length !=3 &&
// (* i vertici[] costituiscono un triangolo non degenere*);
public class Triangolo(Punto vertici[]) throws
TriangoloInvalidoException
//@ ensures (*restituisce la lunghezza del lato minore *)
public float latoMinore()
//@ ensures (*restituisce la lunghezza del lato intermedio *)
public float latoMedio()
//@ ensures (*restituisce la lunghezza del lato maggiore *)
public float latoMaggiore()
...}
33
16
Invariante astratto?
Il triangolo non è degenere:
vale diseguaglianza triangolare e i lati hanno lunghezza >0
//@ public invariant
latoMaggiore()<latoMinore()+latoMedio()
//@ && latoMinore()*latoMedio()*latoMaggiore()>0;
34
Le proprietà astratte sono utili
Gli utilizzatori della classe possono usare le proprietà
come assunzioni sul comportamento della classe
Esempi
Una stack ha proprietà evolutiva LIFO
Un triangolo non è degenere
Un Poly ha sempre grado >=0
Un IntSet non ha size negativa
Per essere davvero utili, però, bisogna essere sicuri che
gli invarianti sono davvero verificati
E possibile verificare questo solo in base alla specifica
35
17
Ragionare al livello dell astrazione
L invariante di Triangolo è verificato dalla specifica?
Occorre avere specifica precisa del costruttore.
Se il costruttore non consente di costruire triangolo
degenere, allora siamo sicuri che invariante vale, in
quanto la classe è pura: una volta costruito, il triangolo
non può essere modificato.
La prova non utilizza l implem. ma solo la specifica
del costruttore
potrebbe essere dim. prima di implementare
Se l implementazione sarà corretta rispetto alla specifica,
l invariante resterà verificato anche sull implementazione
36
Ragionare con classi non pure
Con classi non pure? non basta dimostrare proprietà
per il costruttore, ma occorre considerare i mutator
Metodo induttivo:
1. Invariante vale al termine del costruttore
2. Sotto l ipotesi che l invariante sia verificato al momento
della chiamata, mostrare che vale al termine di ciascun
mutator
Esempio: dimostriamo l invariante pubblico di IntSet
1. Vale per il costruttore (restituisce IntSet vuoto): dim. =0
2. Vale per insert: non può decrementare la dim. di IntSet
3. Vale per remove: remove eseguita solo se elem era già nell
insieme
NB: non occorre dimostrare che l invariante vale per gli
observer (non possono modificare stato)
37
18
Implementazione delle astrazioni
Implementazione di un ADT
L implementazione di un tipo deve fornire:
una rappresentazione (rep) per gli oggetti del tipo, cioè una
struttura dati per rappresentarne i valori
(cioè un insieme di instance variables dichiarate private)
l implementazione di tutte le operazioni (costruttori e metodi)
Esempio:
per IntSet: gli elementi nell insieme cambiano: astrazione è
mutabile. Allora si può pensare di usare un per memorizzare gli
elementi
invece String è immutabile: una sua implementazione può usare
gli array.
39
19
Come scegliere un rep?
Scelta del rep dipende da efficienza, semplicità, riuso di
strutture dati esistenti
un rep per IntSet con liste a puntatori avrebbe la stessa
flessibilità dei ArrayList con efficienza leggermente maggiore
se ci sono molti inserimenti, ma più scomoda da
programmare
un rep diverso per IntSet (array ordinato o albero binario)
potrebbe consentire, se utile, di ricercare se un elemento è
già inserito con una procedura veloce come la ricerca binaria
40
Scelte implementative per IntSet
La rappresentazione scelta per i valori di un ADT può
condizionare l'efficienza di alcune operazioni
Tipicamente, se esistono più rappresentazioni alternative,
conviene realizzare quelle più efficienti per le operazioni più
frequenti
Esempio
nel caso di IntSet è possibile scegliere di rappresentare l insieme
con un vettore che può contenere elementi ripetuti o meno
se posso avere elementi ripetuti, l operazione insert è più veloce
se non posso averli, le operazioni remove e isIn sono più
efficienti
poiché normalmente l operazione isIn è più frequente, la
seconda scelta è più conveniente.
Adottiamo quindi l ipotesi: in IntSet è implementato con un
ArrayList di Integer, in cui non ci sono elementi ripetuti
41
20
Implem. di IntSet con ArrayList
public class IntSet {
private ArrayList<Integer> els; //rappresentazione (rep). els è detta instance
variable.
public IntSet() { els = new ArrayList <Integer>();}
public int size() {return els.size();}
public int choose() throws EmptyException {
if(els.size()==0) throw new EmptyException( IntSet: choose );
else return els.get(els.size()-1);
}
//@assignable \nothing; specifica di metodo private può accedere a elementi private
//@ensures (this.isIn(x)) ? els.get(\result) == x : \result == 1;
/*@ helper @*/ private int getIndex(Integer x) {...//utile per implem. remove, insert, isIn
}
public void insert(int x){if (getIndex(x)<0) els.add(y); } // evita duplicazioni
public boolean isIn (int x){return getIndex(x) >= 0;}
42
Ipotesi soggiacenti a rep, e relazione
fra rep e specifica
els e sempre <> null
in questo modo tutti i metodi che richiamano metodi su els non
hanno bisogno di verificare se els<> null
Insieme vuoto corrisponde a ArrayList els vuoto:
els.size() ==0
in els non ci sono mai due Integer con lo stesso valore
Altre ipotesi (es. per implementare getIndex):
in els non ci sono buchi , cioe posizioni che non contengono
elementi (vale a dire, nessun elemento in els è null)
43
21
Implementazione di Poly: rep
Poly sono immutabili: rappresentabili con array
Dimensione dell array dipende dal grado del polinomio
E più comodo memorizzare anche il grado
public class Poly {
final private int[] trms; //array per memorizzare i coeff.
final private int deg; //grado del polinomio
}rep
Ipotesi sul rep:
i-esimo elem. dell array è coeff. del termine di grado i-esimo (xi)
quindi polinomio di grado n e implementato con array di
dimensione n+1: elemento trms[0] per termine di grado 0,
elemento trms[1] per termine di grado 1, ecc.
44
Implementazione di Poly: costruttori
Polinomio zero è monomio di grado 0 e coefficiente 0. Allora basta array di dim 1
e coeff e grado = 0:
//@ ensures this.degree()==0&& this.coeff(0)==0;
public Poly() {trms=new int[1]; //NB: deg è gia inizializzato a 0
Monomio: coerentemente con ipotesi, monomio di grado n e coeff c e
rappresentato da trms di dimensione n+1, con unico coeff<>0: trms[n] = c
//@ ensures n>=0 && degree()==n && coeff(n)==c &&
//@ (\forall int i; 0<=i<degree; coeff(i) ==0);
//@ signals (NegativeExponentException e) n < 0;
public Poly(int c, int n) throws NegativeExponentException {
if (n<0) throw new NegativeExponentException( Poly(int, int) constructor );
if (c ==0) {trms=new int[1]; deg = 0; return;}
trms = new int[n+1];
trms[n] = c; deg = n;
}
Soluzione e poco efficiente per i monomi (es. 3x700 richiede array grande, quasi
interamente vuoto), ma consente di programmare in modo semplice i produttori.
Implementazione ipotizza che se coeff è 0 allora si costruisce il monomio zero
(per risparmiare tempo e memoria). Quindi la rappresentazione del monomio
45
zero è unica.
22
Implementazione di Poly: produttori
//@ ensures q!=null && (* \result == this + q *);
//@ signals (NullPointerException e) q == null;
public Poly add(Poly q) throws NullPointerException {
if (q==null) throw new NullPointerException( add );
Add costruisce nuovo Poly r del degree massimo fra this e q, ottenuto come
monomio con coeff. 1:
Poly r = new Poly(1,Math.max(q.deg, deg));
Add poi mette in r.trms la somma dei coefficienti dello stesso grado:
for (int i=0; i<=Math.min(deg, q.deg); i++)
r.trms[i]= q.trms[i] + trms[i];
Infine copia in r i termini rimasti (di grado maggiore)
if (q.deg<deg) for (int i=q.deg+1; i<=deg; i++) r.trms[i]= trms[i];
else for (int i=deg+1; i<= q.deg; i++) r.trms[i]= q.trms[i];
return r;}
46
Implementazione della Pila: flex. array
public class Pila<T> {
private T[] el;
private int cima=0;
public int size(){return cima;}
public /*@ pure @*/ T top(){return T[cima];}
public Pila() { el=new T[10];}
public void push(T x) {
if (cima == el.length -1) el=Arrays.copyOf(el,el.length*2); //se l array e
esaurito
el[cima++]= x;
}
public void pop(){cima--;}
}
Naturalmente sarebbe meglio usare una collezione di Java, come LinkedList
47
23
Relazioni fra Astrazione e
Implementazione
Funzione di Astrazione
Stati astratti (della specifica) vs. stati concreti (del rep):
es. per IntSet: {1,2,5} è uno stato astratto, mentre il valore [1,5,2]
(del vettore els del rep) è uno stato concreto
Funzione di astrazione AF: ConSt
AbsSt
Associa a ogni stato concreto c ConSt al più uno stato astratto
AF(c) (è una funzione, totale o parziale)
AF (di solito) non iniettiva: molti stati concreti possono essere
associati allo stesso stato astratto (es. [1,2,5] e [2,1,5]
corrispondono allo stesso stato astratto {1,2,5})
AF
Stati
Astratti
Stati
Concreti
49
24
Definizione di AF: IntSet
Funzione di astrazione definisce il significato della
rappresentazione
Es.: stabilisce come ogni oggetto della classe IntSet implementa
un oggetto dell astrazione IntSet insieme di interi
Avevamo scritto:
Insieme vuoto e il ArrayList vuoto, con els.size() ==0
els contiene tutti gli int dell insieme che rappresenta, ma
memorizzati come Integer
Come definirla
50
Definire AF con un invariante
Un invariante è una proprietà sempre vera negli stati
osservabili della computazione
un invariante dichiarato private è autorizzato a usare
anche le parti private (metodi puri e attributi) della classe
Si può usare un invariante per descrivere una relazione
(funzione) fra le parti private e i metodi observer della
classe
Per IntSet AF può essere scritta come:
/*@private invariant
@ (\forall int i;; this.isIn(i)<==>els.contains(i))
@*/
51
25
Implementare AF
Scopo: cercare errori nel codice
public String toString() è metodo predefinito della
classe Object, che va sempre ridefinito
NB: implementazione di default: nome dell oggetto + hashcode
Scopo di toString() è restituire una rappresentazione
testuale dell oggetto astratto (utile anche per output)
Quindi toString() mappa l oggetto concreto this
nell oggetto astratto
52
Come definire toString: IntSet
il metodo toString tipicamente deve restituire (il nome
della nuova classe seguito da) una rappresentazione
testuale del valore dell oggetto. Per esempio:
IntSet: {1, 7, 3}
public String toString() {
if (els.size()==0) return IntSet: {} ;
String s = IntSet: { + els.elementAt(0).toString();
for (int i = 1; i<els.size(); i++)
s = s + , + els.elementAt(i).toString();
return s + } ;}
53
26
Esempio di AF: Poly
Poly di grado n implementato con vettore trms di
dimensione n+1 che contiene coeff. del termine di grado
i-esimo in i-esima posizione, con 0<=i<=n
Quindi coefficiente i-esimo di un Poly c e
c.trms[i],
con 0 <= i <= c.deg. I termini di grado superiore a deg
sono tutti nulli.
/*@private invariant
@ (\forall int i; 0<=i && i<=deg; coeff(i) ==trms[i]) &&
@ (\forall int j; j<0||j>deg; coeff(i) == 0);
@*/
54
Come definire toString: Poly
«coefficiente i-esimo di un Poly c e c.trms[i], con 0 <= i
<= c.deg.i»
public class Poly {
....
public String toString() {
String s = "Poly: "+ trms[0];
for (int i = 1; i<=deg; i++)
s = s + + " + trms[i] + "x^ + i;
return s;
}
55
27
Es. di AF: la classe Pila
public class Pila<T> {
//@List<T> oat;
private T[] el; private int cima=0;
public Pila() {el= new T[10];
}
public void push(T x) { if (cima == el.length -1)
Arrays.copy(el,el.length*2);
el[cima++]= x;
}
public void pop(){cima--;}
}
AF richiede in questo caso di usare l oggetto astr. tipico
private invariant oat.size() == cima &&
(\forall int i; 0<= i && i<cima; oat.get(i) = 56
el[i]);
Riassumendo
AF descrive l interpretazione del rep
associa a ogni oggetto concreto «legittimo» l oggetto
astratto che si intende rappresentare
Si può specificare con un private invariant, che mette
in relazione attributi privati e observer pubblici
Si implementa con toString()
non è definita per gli oggetti illegali
57
28
Rappresentanti legali per oggetti
astratti
Non tutti gli oggetti (concreti) di una classe sono
rappresentanti legali degli oggetti astratti
Es: valore [1,2,2] per vettore els è scorretto, perché si è deciso di
non contenere elementi duplicati
Invece di solito per i tipi record (es. datatbase) tutti i valori sono
corretti
Le rappresentazioni legali sono quelle per cui valgono
tutte le ipotesi sottese all implementazione
vogliamo descrivere in modo preciso queste ipotesi e inserirle
nella documentazione della classe
AF è definita solo per i rappresentanti legali
58
Invariante di rappresentazione
Invariante di rappresentazione ( rep invariant o RI):
predicato J: ConcSt boolean
È vero solo per oggetti legali : J([1,2,2]) = false, J([1,3,2]) = true
Esempio per IntSet: il rep invariant di un oggetto concreto c è
c.els e sempre != null &&
c.els contiene solo elementi != null &&
in c.els non ci sono mai due interi con lo stesso valore
In JML:
//@ private invariant els != null &&
//@ !els.contains(null) &&
//@ (\forall int i; 0 <= i && i<els.size();
//@
(\forall int j; i <j && j<els.size();
//@
!(els.get(i).equals(els.get(j))));
Di norma, gli RI contengono solo riferimenti agli attributi privati della classe:
le relazioni fra oggetto astratto (pubblic) e concreto (private) sono definite
da AF
59
29
Es. RI: la classe Pila
public class Pila<T> {
private T[] el;
private int cima=0;
public Pila() {el= new T[10];}
public void push(T x) { if (cima == T.length -1) Arrays.copy(el,el.length*2);
el[cima++]= x;
}
public void pop(){cima--;}
}
RI:
private invariant el!=null && cima>=0 && cima<=el.length;
60
Precisione del rep invariant
Quando scrivere il rep invariant?
Prima di implementare qualunque operazione
Come scrivere il rep invariant?
private invariant ....
Che cosa scrivere nel rep invariant?
Le proprietà che caratterizzano quegli stati concreti che
rappresentano stati astratti
rep invariant, insieme ad AF, deve includere tutto ciò che serve
per potere implementare i metodi, data la specifica,
Es. Alberto implementa metodi insert e isIn, Bruno
implementa remove e size, ma i due non si parlano: usano
solo il rep invariant!
Es. Per IntSet non basta scrivere els<>null: bisogna anche dire
che non ci sono duplicati
62
30
Validità del rep invariant
Se un metodo è observer, allora non può modificare RI (tuttavia
vedremo effetti collaterali benevoli
RI deve essere valido negli stati osservabili da un utilizzatore
della classe
costruttore: rep invariant deve sempre valere all uscita da un
costruttore (salvo che nel caso in cui il costruttore lanci un eccezione)
per ogni metodo m, se rep invariant è verificato al momento della
chiamata di m, allora rep invariant deve valere anche al momento
dell uscita (return) da m (salvo se m è un metodo private dichiarato
come helper in JML)
Se rep non vale all entrata di un metodo m, allora non si può
garantire nulla (es. se els=null quando si chiama insert, allora RI
continuerà probabilmente a essere false o si avrà un eccezione)
Durante l esecuzione di un metodo RI può diventare falso: deve
essere verificato solo quando si ritorna al chiamante (anche in
caso di eccezione).
Esempio: un implementazione di insert(int x), che inserisce x in ultima
posizione di els e poi verifica se non è duplicato
63
Es. validità del rep invariant
Rep invariant deve sempre valere all uscita da un costruttore
public IntSet() { els = new ArrayList();} //...new ArrayList<Integer>();
all entrata in IntSet() els vale null, violando il rep; all uscita da IntSet(),
els <> null e els.size =0: rep è verificato.
per ogni metodo m (salvo che per gli helper): se rep invariant è
verificato al momento della chiamata di m, allora rep invariant
deve valere anche al momento dell uscita (return) da m
public void insert(int x){
Integer y = new Integer(x); RI: c.els <> null &&
if (getIndex(y)<0) els.add(y); elementi in c.els sono tutti Integer !=null &&
non ci sono elementi duplicati in c.els
}
Se this verificava RI, allora anche all uscita deve valere els <> nul;
Se tutti gli elmenti erano Integer != null, aggiungiamo al piu un
Integer!=null
se tutti gli elementi erano distinti, e getIndex restituisce 1 solo se y
non c e , allora y e aggiunto solo se non c e nessun Integer dello
stesso valore.
64
31
Quando verificare invariante
1. Quando si progetta il codice: implementazione deve
essere scritta in modo da conservare RI. Vantaggi:
più chances che implem. sia corretta, perché RI è preciso;
migliora documentazione: RI spiega al lettore perché le
operazioni sono state implementate in quel modo, senza le
possibili ambiguità della specifica
2. In fase di test: programmi di test possono usare RI
(p.es. scritto in Java con assert) per verificare se
un implementazione rispetta il rep.
Utile per automatizzare testing
65
Implementare RI
il rep
invariant definisce tutte le ipotesi sottostanti
all implementazione di un tipo
definisce quali rappresentazioni sono legali (true), cioè per quali
valori AF è definita
deve essere verificato in tutti gli stati osservabili dagli utilizzatori
si implementa con metodo repOk():
E possibile implementarlo in Java con un metodo ad hoc, detto
repOk():
public boolean repOk() {
// Ensures: restituisce true se rep invariant vale per this,
//
altrimenti restituisce false
...}
66
32
Esempio: repOk per IntSet
public boolean repOk() {
// els <> null && :
if (els == null) return false;
//!els.contains(null) && :
if (els.contains(null)) return false;
//for all integers i, j. (0 <= i< j<c.els.size => c.els[i].intValue !=c.els[i].intValue:
for (int j = i+1; j< els.size(); j++)
if (x.equals(els.get(j))) return false;
}
return true;
}
67
Come verificare RI con repOk
1. Nel codice: inserire il controllo di repOk nei
mutators e nei costruttori, tramite il meccanismo
delle asserzioni
Si chiama assert repOk(); al termine del metodo o del
costruttore: se risultato è true si continua, altrimenti
AssertionError
Molto utile (ma codice più lento!). Esempio per IntSet:
public void insert(int x) {
assert repOk();
return;}
Se repOk() non è verificato, viene generato errore:
AssertionError in function IntSet.insert(int)
68
33
Esempio di verifica di repOk() e di
ensures
public void insert(int x) {
//@ensures this.isIn(x) && (*this include gli elementi in \old(this)*)
assert repOk() : insert(int) non mantiene RI: + x;
assert isIn(x) : this+ insert (int) non mantiene postcond:
+ x;
return;
}
69
Ragionare sulle data abstraction
34
Ragionare sulle Data Abstraction
Scrivendo un programma, spiegandolo o leggendolo, si cerca di
convincersi che sia corretto, ragionandovi
Ragionare su procedure: data precondizione, ci si convince che il
codice della procedura ha gli effetti desiderati
Ragionare su data abstraction è più complicato:
occorre considerare l intera classe e non solo una singola procedura
il codice manipola il rep, ma ci si deve convincere che soddisfa la
specifica degli oggetti astratti
Metodo in due passi:
1. mostrare che implementazione conserva il rep invariant
2. mostrare che le operazioni sono corrette rispetto alla specifica
dell astrazione, eventualmente utilizzando il rep invariant e
proprietà dell astrazione
a) Poi occorre mostrare che le proprietà dell astrazione usate al punto 2
sono valide
71
Conservazione del rep invariant
Sia C una data abstraction. Due passi per mostrare che il
rep invariant si conserva:
1. mostrare che i costruttori ritornano oggetti che
conservano il rep invariant
Esempio: public IntSet() { els = new ArrayList();}
all uscita da IntSet(), els <> null e els.size =0: rep è verificato
2. mostrare che i metodi non-helper mantengono il rep
invariant
Per ogni metodo m, si ipotizza che il rep inv. valga per this e
per quei parametri c1, .., cn che sono proprio di tipo C; si
ipotizza inoltre che valga la precondizione del metodo
Si mostra allora che, quando m ritorna al chiamante, il rep. inv.
vale sia per this che per i parametri c1, .., cn
72
35
Esempio: insert per la IntSet
//@assignable \nothing;
//@ensures (this.isIn(x)) ? els.get(\result) == x
: \result == 1;
/*@ helper @*/ private int getIndex(Integer x) {
for (int i=0; i <els.size(); i++)
if (x.equals(els.get(i))) return i;
return 1;}
NB: questo metodo è private: ensures può usare anche gli attributi private!
RI: els <> null &&
!els.contains(null) &&
«non ci sono elementi duplicati in els»
//@ ensures this.isIn(x)
//@ && (\forall int y; x!=y;
//@ \old(this.isIn(y)) <==> this.isIn(y));
public void insert(int x){//inserisce x in this
Integer y = new Integer(x); //copia x in y
if (getIndex(y) < 0) els.add(y);
}
1.
2.
3.
4.
L invariante vale per this quando insert è chiamata (per ipotesi):
La chiamata a getIndex non modifica invariante (rep non muta)
La insert aggiunge x a this sse x non è già presente in this;
allora il rep invariant continua a valere al termine della chiamata
73
Correttezza delle operazioni
Non basta conservare il rep invariant! Ad esempio:
insert (int x) {} conserva l invariante, ma non inserisce x!
Occorre mostrare che, dato il rep inv., tutte le operazioni
del rep implementano correttamente la specifica.
I costruttori
I metodi
La relazione fra rep e specifica è data da AF
Avendo provato il rep inv., si può ragionare per ogni
operazione in modo indipendente:
la relazione fra le operazioni è catturata dal rep inv.
il rep. inv. è verificato
noto il rep inv, si prova la correttezza di ciascuno metodo
74
36
L implementazione di IntSet è
corretta
Il costruttore public IntSet() { els = new ArrayList();} è corretto:
se els è vuoto, AF(this)= .
Il metodo public int size() {return els.size;} è corretto:
els.size è proprio la cardinalità dell insieme perché:
AF associa tutti e soli gli elementi del vettore agli elementi dell insieme
il rep inv. assicura che non ci sono duplicati in els o elementi =null
//@ ensures !this.isIn(x) &&(\forall int y; x!=y; \old(isIn(y)) <==> isIn(y));
public void remove(int x){
int i = getIndex(new Integer(x));
if (i<0) return;
//se elem. non è in vettore => return
els.set(i,els.lastElement());
//ultimo elemento copiato in pos. i
els.remove(els.size()-1);}
//elimina ultimo elemento.
//NB Più efficiente che rimuovere elemento in posizione i...
75
Effetti collaterali benevoli.
Esposizione del rep
37
Effetti collaterali benevoli
Un astrazione mutabile deve avere un rep mutabile.
Però è possibile che rep muti anche se oggetto astratto
non cambia!
Un implementazione ha un effetto collaterale
benevolo se modifica il rep senza influenzare lo
stato astratto di un oggetto
La modifica non è quindi visibile al di fuori dell implementazione
Di fatto, si passa da una rappresentazione a un altra dello
stesso oggetto astratto, per esempio per motivi di efficienza
In questi casi, un astrazione immutabile può avere un rep
mutabile
Con JML, purtroppo non è consentito avere effetti
collaterali benevoli chiamando metodi puri...
77
Es. di effetto collaterale benevolo
Numeri razionali implementati come coppia di interi: int num, denom;
// Un razionale tipico è un qualunque rappresentante del razionale n/d
// AF(c) = c.num/c.denom
Implementiamo i costruttori senza ridurre num e denom ai minimi termini,
per non perdere tempo all inizializzazione. Allora rep invariant è: denom<>0
Una possibile implementazione del confronto (equals):
riduce entrambi i numeri ai minimi termini (chiamando metodo opportuno) e poi
confronta i due numeratori e i due denominatori. Al termine, num e denom
possono essere cambiati, ma oggetto astratto no.
Esempio:
//qui: x.num =3, x.denom =9, y.num = 2, y.denom = 6
if (x. equals(y))
//qui: x.num = y. num = 1, x.denom = y.denom = 3
Vantaggio: non si perde tempo a semplificare numeri razionali poco usati, ma si
semplifica solo quando un numero è usato con operazioni come equals. Si
potrebbe fare semplificazione anche quando si chiama mult, ecc.
Il tipo può essere immutabile! L oggetto astratto non cambia, solo quello
concreto.
78
38
Esporre parti mutabili del rep
Un implementazione espone il rep quando fornisce
a utenti degli oggetti un modo per accedere a parti
mutabili del rep
È un grave, ma comune, errore di implementazione!!
Questo avviene tipicamente in due modi:
1.Restituendo a un metodo chiamante un riferimento a
una componente mutabile del rep.
2.Inglobando nel rep un componente mutabile per cui
esiste un reference all esterno dell oggetto.
NB: non c e nessun problema a fornire un reference a
una componente immutabile del rep (tanto essa non
può mutare)
79
Es. esposizione rep con return
Se classe IntSet avesse il metodo:
//@ensures (* \result è un vettore con tutti gli elementi
//@in this, senza ripetizioni e in ordine arbitrario *);
public ArrayList allEls() {
return els; //ERRATO!!!
}
Utilizzatori della classe possono chiamare allEls e accedere
all implementazione: possono ad esempio inserire elementi duplicati e
distruggere il rep invariant!
IntSet o = new IntSet....//o inizializzato a un insieme opportuno
ArrayList v = o.allEls();
v.insert(new Integer(4));
v.insert(new Integer(4)); //inserisce due copie di 4
In generale, gli utilizzatori potrebbero utilizzare v per costruire codice dipendente
dall implementazione, vanificando la separazione specifica/implementazione
Soluzione corretta: restituire una copia di els: return els.clone();
il chiamante a quel punto può agire solo sulla copia, senza potere riferire l oggetto
originale
80
39
Esp. rep da parametro in input
//@ ensures (* this è l insieme di tutti gli Integer in elms *);
//@ signals (NullPointerException e) elms == null;
public IntSet(ArrayList elms) throws NullPointerException {
if (elms == null)
throw NullPointerException ( Intset 1 argum. constructor )
els = elms; // ERRATO!!
}
1. Nel rep viene memorizzato un riferimento a un oggetto esterno, esponendo il rep.
2. La specifica del costruttore è corretta, ma la sua implementazione è sbagliata,
perche non rispetta il rep invariant: un utente di IntSet può passare un ArrayList
di Integer con duplicati e costruire un oggetto IntSet con rep invariant falso! Questo
anche se alla chiamata di IntSet(ArrayList) il parametro non avesse duplicati
Altra soluzione scorretta: els = elms.clone(); //non controlla se elms verifica RI!
Implementazione corretta: chiamare la insert per ogni elemento del parametro
elms.
Altro esempio: restituire un riferimento all array trms del rep della classe Poly: il tipo
Poly è dichiarato immutabile ma l utilizzatore di Poly lo può modificare!
81
Come non esporre il rep
Dichiarare tutti gli attributi private (o al massimo protected
o friend)
Se gli attributi riferiscono oggetti mutabili (es. els e un
ArrayList, quindi mutabile):
- Non restituire reference a oggetti mutabili.
- se necessario, creare copie degli oggetti interni
mutabili per evitare di restituire gli originali
attraverso i metodi
- Non salvare ref. a oggetti esterni mutabili passati al
costruttore.
- Se necessario fare copie e salvare ref. alle copie.
82
40
Astrazioni sul controllo
Specifica e Implementazione degli Iteratori
Nuove iterazioni
Definendo un nuovo tipo come collezione di oggetti (p. es., set) si
vorrebbe disporre anche di un operazione che consenta cicli
(iterazioni)
Es.: gli elementi possono essere stampati, oppure sommati tra loro,
oppure confrontati per cercare il più piccolo o il più grande, ecc.
Es. IntSet: l utilizzatore ha bisogno di verificare se tutti gli elementi
sono positivi
Se la collezione è un Vector, un array o un ArrayList, è facile
Es. stampare tutti gli elementi del vettore vect, dal primo all'ultimo:
for (int i = 0; i < vect.size(); i++) System.out.println(vect.get(i));
array e vettori: organizzazione lineare degli elementi, e possiamo
accedere ad un qualunque elemento usando un indice
Con altri contenitori/collezioni? Con IntSet? non c è get o size
Information hiding non consente (giustamente!) l accesso diretto al rep
di un contenitore! Non possiamo scrivere, se set è un IntSet:
for (int i = 0; i < set.els.size(); i++) System.out.println(set.els.get(i));
Possibilità: fare choose su una copia di set, fare verifica e poi
remove... molto inefficiente
84
41
Iterare su collezioni
Soluzione poco generale: scrivere metodi in IntSet per stampare e
per verificare se elementi positivi
come fare a prevedere in anticipo tutti gli usi di IntSet? Es. calcolare la
somma di tutti gli elementi
Soluzione inefficiente : IntSet ha un metodo che restituisce un
array con tutti gli elementi
si consuma spazio e tempo di memoria (es. ricerca che si interrompe al
primo elemento...). A volte però è utile.
Soluzione inefficiente: fornire anche IntSet di size e di get
for (int i = 0; i < set.size( ); i++)
System.out.println(set.get(i));
con IntSet funzionerebbe bene finché si usa Vector. Ma se poi si
cambiasse implementazione? se fosse con lista... Su molte strutture dati
accesso casuale a i-esimo elemento può essere molto inefficiente! Es.
con linked list, occorre sempre scorrere gli elementi precedenti a quello
in posizione i: numero accessi alla lista tramite la get(i): 1 + 2 + 3+ ... + n
85
= n(n+1)/2, con n = set.size().
Iteratori
Soluzione generale: introdurre oggetti detti iteratori in grado di
spostarsi sugli elementi dei contenitori
Associare a ogni contenitore un iteratore
Iteratore rappresenta un'astrazione del concetto di puntatore a un
elemento del contenitore e permette di scorrere il contenitore senza
bisogno di conoscere l'effettivo tipo degli elementi.
Es. IntSet: oggetto iteratore su IntSet con metodi: next() per restituire
l elemento su cui si è posizionati e muoversi su quello successivo;
hasNext() per verificare se siamo sull ultimo elemento.
public static boolean insiemePositivo(IntSet set){
genera Iteratore itr per set (posizionato al primo elemento) ;
while (itr.hasNext( ))
if (itr.next()) <0) return false;
return true;}
86
42
Da Eckel, Thinking in Java:
There s not much you can do with the Java Iterator
except:
Ask a container to hand you an Iterator using a method called
iterator( ). This Iterator will be ready to return the first
element in the sequence on your first call to its next( ) method.
Get the next object in the sequence with next( ).
See if there are any more objects in the sequence with
hasNext( ).
Remove the last element returned by the iterator with
remove()
87
Interface Iterator
Per standardizzare l uso degli iteratori, in Java c è interfaccia Iterator in
java.util (dove si trova anche la run-time exception
NoSuchElementException)
public interface Iterator<E> {
//@ensures (* \result è true se e solo se esiste un prossimo elemento *);
public boolean hasNext ( );
//@ensures (* fornisce il prox elemento se esiste *);
//@ signals (NoSuchElementException e) (*non esiste un prox. elem. *);
public E next() throws NoSuchElementException;
//@ensures (* rimuove ultimo elemento ritornato da next() *);
public void remove ( );
//OPZIONALE, nel senso che si può implementare senza rimozione
}
88
43
Interfaccia Iterable (1)
Per standardizzare l uso degli iteratori, in Java esiste anche
l interfaccia Iterable (in java.lang)
public interface Iterable <T> {
//@ ensures (* restituisce un iteratore su una collezione di
//
elementi di tipo T *);
public Iterator <T> iterator ();
}
Le classi che implementano l interfaccia Iterable sono
tipicamente collezioni che forniscono un metodo di nome
standard iterator() per ottenere un oggetto iteratore, con cui
scandirne gli elementi
89
Interfaccia Iterable (2)
Un contenitore che implementa l interfaccia Iterable può essere
argomento di un istruzione for-each
il codice
for ( Tipo variabile : oggettoContenitore )
corpoDelCiclo
viene tradotto dal compilatore nel seguente
Iterator <Tipo> iter = oggettoContenitore.iterator();
while ( iter.hasNext() ) {
Tipo variabile = iter.next();
corpoDelCiclo;
}
E necessario implementare Iterable se si vuole costruire una
collezione da usare con for-each
è un utile semplificazione per gli utenti della collezione: per iterare su di
essa, se non devono fare remove e non vogliono seguire un ordine
particolare, non devono generare esplicitamente un iteratore, perchè
usando un ciclo for-each lo fanno fare al compilatore
il prezzo della semplificazione lo paga l implementatore, che deve
codificare il metodo iterator()
90
44
Uso iteratore per IntSet
public class IntSet implements Iterable<Integer> {
}
public static boolean stampa(IntSet set){
for (Integer i : set) System.out.println(i);
}
public static boolean insiemePositivo(IntSet set){
for (Integer i : set)
if (i <0) return false;
return true;}
91
Generatori e iteratori; remove
Nomenclatura standard:
iteratore è oggetto che consente di muoversi su collezioni
Interfaccia Iterator è in java.util e fornisce anche remove()
(opzionale)
Nomenclatura Liskov:
generatore è oggetto che consente di muoversi su collezioni
iteratore è un metodo che restituisce un generatore
Interfaccia Iterator non ha remove()
93
45
Specifica: metodi iteratori per Poly e
IntSet
public class Poly implements
Iterable {
}
public Iterator<Integer>iterator( )
//@ensures (* \result è un
//@generatore che dà gli
//@esponenti dei termini non
//@zero di this *);
}
public class IntSet implements
Iterable {
public Iterator<Integer> iterator( )
//@ensures (* \result è un
//@generatore che
//@dà i valori contenuti in this,
//@ciascuno 1 sola volta
//@requires (* this non deve
//@essere modificato quando
//@generatore è in uso *);
94
Implementazione con classe interna
public class Poly implements Iterable {
private int [] trms;
PolyGen associata
solo alla classe Poly, non a
private int deg;
un ogg. Poly: può
public Iterator<Integer> iterator() {
accedere solo alle parti
return new PolyGen(this);
static di Poly.
}
Qui è opzionale.
//classe interna
private static class PolyGen implements
Iterator<Integer> {
Implementa
...
interfaccia
non posso generare esemplari
}
Iterator: ha
fuori dalla classe Poly, ma
next(),
PolyGen può accedere ai private di un Poly
hasNext()
95
//tutte le altre operazioni della classe
}
46
La classe interna per l iteratore
private static class PolyGen implements Iterator<Integer> {
private Poly p; //il Poly da iterare
Implementa
private int n; //prox el. da considerare
interfaccia
PolyGen(Poly pol) {
Iterator:
ha next(),
//@requires pol!=null;
hasNext()
p=pol;
if(p.trms [0]==0)n=1; else n=0;
//n=1 serve per hasNext() se p.deg ==0
}
public boolean hasNext ( ) {return n<=p.deg;}
public int next ( ) throws NoSuchElementException {
for (int e=n; e<=p.deg; e++)
if (p.trms[e] != 0) {n=e+1; return new e;}
throw new NoSuchElementException("Poly.terms");
}
}
96
RI e AF per iteratori: Es. PolyGen
RI: espresso in termini delle instance variables di
PolyGen
//@ private invariant p != null && 0 <= n && n <= p.deg+1;
AF: L oggetto astratto è la sequenza degli elementi
ancora da generare
Quindi l oat è sequenza,in ordine crescente, che contiene tutti
e soli gli elementi 0 di p.trms che occupano posizione j >=n,
nello stesso ordine
//@ List<Integer> oat;
//@ private invariant
//@(forall int i; 0<=i && i <oat.size()-1; oat.get(i)<oat.get(i+i)) &&
//@ (forall Integer x;; oat.contains(x) <==> p.trms[x]!=0 && x>=n)
97
47
Osservazioni
An iterator is an object whose job is to move through a sequence
iterator is usually what s called a light-weight object: one that s
cheap to create
Collezione può avere più tipi di iteratori (e quindi più metodi per
generarli)
Esempio:
//@ensures (* \result è generatore dall elemento più piccolo al più grande *);
public Iterator<Integer> smallToBig() {}
//@ ensures (* \result generatore dall elemento più grande *);
public Iterator<Integer> bigToSmall() {}
//@ensures (* \result è generatore dal primo elemento
//@ nell ordine di inserimento *);
public Iterator<Integer> first() {
98
Collezioni mutabili e metodo remove
remove è un operazione opzionale : se non è implementata,
quando invocata viene lanciata UnsupportedOperationException
In effetti, raramente è utile modificare una collezione durante
un iterazione: più spesso si modifica alla fine (e.g., trovo elemento e
lo elimino, poi iteratore non più usato)
Semantica di remove() assicurata dal contratto dell interfaccia:
elimina dalla collezione l ultimo elemento restituito da next(), a patto che
venga chiamata una sola volta per ogni chiamata di next()
collezione non venga modificata in qualsiasi altro modo (diverso dalla
remove) durante l iterazione
altrimenti
lanciata eccezione IllegalStateException se metodo next() mai
chiamato o se remove() già invocata dopo ultima chiamata di next()
semantica della remove() non specificata se collezione modificata in
altro modo (diverso dalla remove) durante l iterazione
vincolo ragionevole: è prevedibile che una modifica durante un iterazione
lasci la collezione in uno stato inconsistente
99
48
Iteratori "stand alone"
Un iteratore potrebbe NON far parte di una classe, ma
essere una procedura statica "stand alone"
Per esempio, iteratore allFibos che genera tutti i numeri
di Fibonacci
o generatore di numeri primi del testo
In questo caso, è necessario definire la classe interna
come static: un metodo static di una classe non può
accedere a un costruttore di una classe interna non
static
100
public class Num {
public static Iterator allFibos( ) {
return new FibosGen( );
}
static necessario:
Il suo costruttore è chiamato da
un metodo statico!
//classe interna
private static class FibosGen implements Iterator<Integer> {
private int prev1, prev2; //i due ultimi generati
private int nextFib; //nuovo # Fibonacci generato
FibosGen( ) {prev2=1; prev1=0;}
public boolean hasNext ( ) {return true;}
public Integer next ( ) {
nextFib=prev1+prev2;
prev2=prev1; prev1=nextFib;
return Integer(nextFib);
}
}
}
101
49
Uso dell iteratore per i # Fibonacci
//@ensures (*stampa tutti # Fibonacci <=m in ordine
//@ crescente *);
public static void printFibos(int m); {
Iterator<Integer> g = Num.AllFibos();
int p = g.next();
if (p > m ) return;
System.out.println( prox Fibonacci e`
+ p);
}
}
102
50