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
© Copyright 2024 Paperzz