Trasformare array paralleli in array di oggetti -...

27
1 Trasformare array paralleli in array di oggetti Trasformare array paralleli in array di oggetti Un array è una struttura di dati omogenea: gli elementi dell’array sono tutti dello stesso tipo (che è il tipo dell’array). A volte è necessario gestire informazioni di tipo diverso ma riferite allo stesso concetto. Supponiamo di voler memorizzare delle informazioni riguardanti gli impiegati di una ditta: nome, stipendio, età. Possiamo pensare di costruire tre array distinti, uno per ogni tipo di informazione. (Consigli per la qualità 7.2) Trasformare array paralleli in array di oggetti String nome[] = new String[1000]; double stipendio[]= new double[1000]; i int eta[]= new int[1000]; Per avere informazioni nome stipendio età sull’impiegato i-esimo accediamo alla componente i-esima di ciascuno dei tre array. Trasformare array paralleli in array di oggetti I tre array sono strettamente correlati tra loro: devono avere la stessa lunghezza • un algoritmo che elabora le informazioni su un impiegato deve avere i tre array tra i suoi parametri • se si volesse aggiungere un’ulteriore informazione, si dovrebbe tenere presente l’organizzazione comune ai tre array (l’i-esimo impiegato sta all’i- esimo posto). Linguaggi come Java mettono a disposizione la possibilità di considerare l’impiegato come un concetto e di considerarlo un’unica informazione suddivisa in tre campi. Trasformare array paralleli in array di oggetti Questo tipo di informazione nei linguaggi di programmazione si chiama record e rappresenta una collezione di elementi di tipo diverso. La parola record (registrazione) è una parola “antica” dei linguaggi di programmazione, così come la parola file (archivio). Sono parole nate in riferimento alla registrazione di dati su supporti fisici come i nastri magnetici (o i dischi); l’archivio che contiene l’insieme delle informazioni registrate prendeva il nome di file di dati. Trasformare array paralleli in array di oggetti Per realizzare il concetto “impiegato” possiamo utilizzare una classe, i cui campi saranno le informazioni che caratterizzano l’impiegato: public class Impiegato{ String nome; double stipendio; int eta; }

Transcript of Trasformare array paralleli in array di oggetti -...

1

Trasformare array paralleli in array di

oggetti

Trasformare array paralleli in array di oggetti

• Un array è una struttura di dati omogenea: gli elementi dell’array sono tutti dello stesso tipo (che è il tipo dell’array).

• A volte è necessario gestire informazioni di tipo diverso ma riferite allo stesso concetto.

• Supponiamo di voler memorizzare delle informazioni riguardanti gli impiegati di una ditta: nome, stipendio, età. Possiamo pensare di costruire tre array distinti, uno per ogni tipo di informazione. (Consigli per la qualità 7.2)

Trasformare array paralleli in array di oggetti

String nome[] = new String[1000];

double stipendio[]=

new double[1000]; i

int eta[]= new int[1000];

Per avere informazioni nome stipendio età

sull’impiegato i-esimo accediamo alla componentei-esima di ciascuno dei tre array.

Trasformare array paralleli in array di oggetti

• I tre array sono strettamente correlati tra loro:• devono avere la stessa lunghezza • un algoritmo che elabora le informazioni su un

impiegato deve avere i tre array tra i suoi parametri • se si volesse aggiungere un’ulteriore informazione,

si dovrebbe tenere presente l’organizzazione comune ai tre array (l’i-esimo impiegato sta all’i-esimo posto).

• Linguaggi come Java mettono a disposizione la possibilità di considerare l’impiegato come un concetto e di considerarlo un’unica informazione suddivisa in tre campi.

Trasformare array paralleli in array di oggetti

• Questo tipo di informazione nei linguaggi di programmazione si chiama record e rappresenta una collezione di elementi di tipo diverso.

• La parola record (registrazione) è una parola “antica” dei linguaggi di programmazione, cosìcome la parola file (archivio). Sono parole nate in riferimento alla registrazione di dati su supporti fisici come i nastri magnetici (o i dischi); l’archivio che contiene l’insieme delle informazioni registrate prendeva il nome di file di dati.

Trasformare array paralleli in array di oggetti

• Per realizzare il concetto “impiegato”possiamo utilizzare una classe, i cui campi saranno le informazioni che caratterizzano l’impiegato:public class Impiegato{

String nome;

double stipendio;

int eta;

}

2

Trasformare array paralleli in array di oggetti

• Possiamo costruire un array ditta le cui componenti sono di tipo Impiegato:

Impiegato ditta[] =

new Impiegato[100];

• In tale modo invece di avere tre array paralleli abbiamo un array di record (detto anche tabella) e per accedere all’i-esimo impiegato utilizzeremo la componente i-esima dell’array:

ditta[i]

Trasformare array paralleli in array di oggetti

• Per accedere ai campi si può scrivereditta[i].nome

ditta[i].stipendio

ditta[i].eta

• Se vogliamo seguire le regole della programmazione ad oggetti (incapsulamento), definiamo private i campi della classe Impiegato, dovremo allora costruire dei metodi di accesso:

ditta[i].nome()

ditta[i].stipendio()

ditta[i].eta()

Trasformare array paralleli in array di oggetti

• Esercizio. Costruire e stampare una tabella archivio per contenere le informazioni sugli studenti iscritti ad un corso: il loro numero di matricola termina con 2 o 3.

• Soluzione. Costruiamo una classe Stud che contiene informazioni minime su uno studente:• nome• cognome• matricola

• La classe conterrà il costruttore e i metodi di accesso; poi costruiremo una classe di prova.

Trasformare array paralleli in array di oggetti

/**Classe minima per uno studente:

contiene il nome e cognome e la matricola dello studente e i metodi per l'accesso ai campi */

public class Stud {

private String nome;

private String cognome;

private int matricola;

public Stud (String n, String c, int m){

nome = n;

cognome = c;

matricola = m;

}//fine costruttore

Trasformare array paralleli in array di oggetti

/**restituisce il numero di matricola*/

public int matricola () {

return matricola;

}

/**restituisce il nome */

public String nome () {

return nome;

}

/**restituisce il cognome */

public String cognome () {

return cognome;

}

}//fine Stud

Trasformare array paralleli in array di oggetti

import java.util.Scanner;

public class ProvaStud{

public static void main(String[] args) {

Scanner in = new Scanner(System.in);

Stud corso23[]= new Stud [120];

int i = 0;

String nome, cognome;

int matricola;

while(in.hasNext()) {

nome = in.next();

cognome = in.next();

matricola = in.nextInt();

3

Trasformare array paralleli in array di oggetti

if(matricola%10==2 || matricola%10==3){

corso23[i]=

new Stud(nome,cognome, matricola);

i++;

}

else System.out.println("\nlo studente “

+ cognome + " " + nome +

" appartiene ad altro corso");

}

Trasformare array paralleli in array di oggetti

System.out.println("\nStampa archivio");

for (int k=0 ;k<i;k++){

System.out.print(corso23[k].nome()+" ");

System.out.print(corso23[k].cognome());

System.out.println

(" " + corso23[k].matricola());

}//fine for

}//fine main

}

Trasformare array paralleli in array di oggetti

• Attenzione.• I dati nel file devono essere inseriti seguendo

l’ordine di acquisizione dei dati: scambiare il nome con il cognome è un errore logico.

• Nella classe Stud abbiamo:private String nome; //nome è uno scalare

public String nome() {//nome è un metodo

return nome;}

• In Java si può. NON è così nella maggior parte dei linguaggi.

Trasformare array paralleli in array di oggetti

• Attenzione.• Abbiamo costruito un array di oggetti:• definizione dell’array:

Stud corso23[]= new Stud[120];

• costruzione della componente i-esima (è un oggetto):corso23[i]=

new Stud(nome,cognome, matricola);

Ereditarietà

Ereditarietà

• Ereditare significa utilizzare beni di altri in maniera lecita: i beni diventano una nostra proprietà e non appartengono più al vecchio proprietario.

• Nella programmazione orientata agli oggetti ereditare significa utilizzare metodi e variabili di una classe superiore.

• L’ereditarietà è il paradigma (metodologia) che consente il riutilizzo di codice giàesistente. (Capitolo 10)

4

Ereditarietà• L’ereditarietà si utilizza quando si deve

realizzare una classe ed è già disponibile un’altra classe che rappresenta un concetto più

generale.

• Si parla di sviluppo incrementale delle classi: data una classe, che rappresenta la struttura e la funzionalità di un insieme di oggetti, si vogliono aggiungere altre funzionalità e si vuole arricchire la struttura (lo “stampo” si arricchisce di particolari).

Ereditarietà

• Abbiamo già accennato che le eccezioni ArrayIndexOutOfBoundException

StringIndexOutOfBoundException

sono sottoclassi di una classe più generale, IndexOutOfBoundException che indica un accesso errato ad una posizione non valida: nelle due sottoclassi si specifica la posizione errata negli oggetti di tipo array e stringa.

• Alla classe IndexOutOfBoundExceptionviene “aggiunta” la gestione per il tipo specifico.

Ereditarietà

• Una delle caratteristiche e dei vantaggi dell’ereditarietà è che:

il codice che si vuole utilizzare non deve essere riscritto.

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

Ereditarietà

• Abbiamo una classe A che rappresenta un concetto generale; consideriamo un concettoparticolare B: in termini di sottoinsiemi

B⊂⊂⊂⊂ A

Ad esempio, i numerinaturali pari hanno unaproprietà in più rispettoai numeri naturali:Pari = {m = 2*n | n ∈ N}

AB

Ereditarietà

• La classe B eredita i metodi e le variabilidella classe A (non i costruttori).

• Si dice che B “estende” A, nel senso che ha più proprietà, fa più cose, è più particolare.

• In Java si scriveB extends A

• Vediamo attraverso un esempio i vantaggi della possibilità del riutilizzo del codice.

Ereditarietà

• Supponiamo di voler realizzare una classe che rappresenta un conto bancario di risparmio: questo è un conto bancario (ha le stesse caratteristiche) ma possiede una proprietà in più, un tasso fisso di interesse che viene stabilito all’apertura del conto.

• Abbiamo bisogno di un nuovo metodo per calcolare e accreditare gli interessi sul conto.

• Chiamiamo ContoRisparmio la nuova classe e costruiamola a partire da ContoBancario.

5

Ereditarietàpublic class ContoBancario{//CB

private double saldo;

public ContoBancario(){//costruttore

saldo = 0; }

public void deposito(double denaro){

saldo = saldo + denaro;

}

public void prelievo(double denaro){

saldo = saldo - denaro;

}

public double rendiSaldo(){

return saldo;

}

}//fine ContoBancario

Ereditarietàpublic class ContoRisparmio{//CR

private double saldo; //come in CB

private double tassoInteresse;//nuovo campo

//costruttore per CR

public ContoRisparmio(double tasso){

saldo = 0;

tassoInteresse = tasso;

}

//nuovo metodo per CR

public void aggiungiInteresse(){

double interesse =

saldo * tassoInteresse /100;

saldo = saldo + interesse;

}

Ereditarietà

//metodi come in CB

public void deposito(double denaro){

saldo = saldo + denaro;

}

public void prelievo(double denaro){

saldo = saldo - denaro;

}

public double rendiSaldo(){

return saldo;

}

}//fine ContoRisparmio

Ereditarietà

• Abbiamo ottenuto già un vantaggiosemplicemente ricopiando del codice esistente.

• Però possiamo e vogliamo fare di più.• Caso1.• Cosa accade se aggiungiamo una modifica a

CB: ad esempio un controllo che il saldo non diventi negativo?

• Dovremmo non solo aggiungere le modifiche a CB, ma anche a CR (e ad altre sottoclassi).

Ereditarietà

• Caso2.• Come facciamo ad usare classi già esistenti

(della libreria di Java o di particolari pacchetti di software) per costruire un nostro nuovo concetto? Vogliamo utilizzarne i metodi, ma non ne conosciamo il codice: conosciamo solo la loro firma e la loro funzionalità.

• Il linguaggio Java permette di poter dichiarareche CR è sottoclasse (o classe derivata da) di CB.

Ereditarietà

• La classe CR eredita campi e metodi di CB che non devono essere né dichiarati né riscritti: significa che possiamo scrivere in CR solo i nuovi metodi e i nuovi campi.

• Sintassi.public class Sottoclasse extends

Superclasse {

//nuovi campi

//costruttore della Sottoclasse

//nuovi metodi

}

6

Ereditarietà

• In tale modo otteniamo la nuova classe, senzadovere scrivere due volte il codice.

• Il vero riutilizzo del codice consiste non nel ricopiare codice già scritto, ma nello scrivere solamente i nuovi campi e i nuovi metodi, sottintendendo il codice della superclasse.

• La classe ContoRisparmio (che estende CB) si può scrivere nel modo seguente.

Ereditarietàpublic class ContoRisparmio extends

ContoBancario{

private double tassoInteresse;

//nuovo campo

//costruttore di CR

public ContoRisparmio(double tasso){

//codice costruttore

}

//nuovo metodo per CR

public void aggiungiInteresse(){

//codice nuovo metodo

}

}//fine ContoRisparmio

Ereditarietà

• Attenzione.• Dobbiamo però fare attenzione alle

“sovrapposizioni”, che nel caso di scrittura completa della sottoclasse non si presentano.

• Per poter scrivere correttamente il nuovo metodo aggiungiInteresse dobbiamo tener presente che il metodo utilizza il campo saldo che è una variabile privata di CB.

Ereditarietà

• La classe CR eredita il campo saldo che non viene dichiarato ma che “esiste”: un oggetto di tipo CR possiede i campi e i metodidi CR e di CB.

saldodepositoprelievorendiSaldo

tassoInteresseaggiungiInterersse

Ereditarietà

• Quando costruiamo un oggetto c1 di tipo CR, che estende CB, possiamo scrivere:

c1.deposito(500);

c1.aggiungiInteresse();

perché deposito è ereditato da CB e aggiungiInteresse è di CR: chiaramente saldo deve essere unico e deve essere quello di CB (altrimenti rappresenterebbe un nuovo campo, come tassoInteresse).

Ereditarietà

• Si pone infatti il problema:• se in CR usiamo saldo il compilatore

segnala errore perché esiste ma non èvisibile, essendo una variabile privata.

• se la dichiariamo avremo un oggetto con due campi saldo; infatti il metodo aggiungiInteresse utilizza il suo saldo (quello eventualmnete dichiarato in CR) e il metodo deposito utilizza il saldo di CB: questo è un errore logico

perché un conto ha un unico saldo.

7

Ereditarietà

• Soluzione del problema.• Per accedere a saldo di CB abbiamo il

metodo pubblico rendiSaldo; per depositare denaro abbiamo il metodo pubblico deposito.

• Utilizziamo questi metodi all’interno di aggiungiInteresse.

• Pertanto il codice del metodo aggiungiInteresse sarà:

Ereditarietà//codice corretto per CR extends CB

public void aggiungiInteresse(){

double interesse = rendiSaldo() *

tassoInteresse/100;

deposito(interesse);

}

• Dobbiamo scrivere il codice del costruttore di CR. Se non specifichiamo nulla per saldo , si attiva il costruttore della superclasse CB (CB ha un costruttore senza parametri che inizializza a 0 saldo), perciò inizializziamo solamente tassoInteresse.

Ereditarietàpublic class ContoRisparmio extends

ContoBacario{

private double tassoInteresse;

//costruttore di CR

public ContoRisparmio(double tasso){

tassoInteresse = tasso;

}

//nuovo metodo per CR

public void aggiungiInteresse(){

double interesse = rendiSaldo() *

tassoInteresse /100

deposito(interesse);

}

}//fine ContoRisparmio

Ereditarietà

• Una classe di prova per CR avrà istruzioni del tipo:ContoRisparmio contoris =

new ContoRisparmio(2);

//tasso di interesse 2%

contoris.deposito(500); //metodo di CB

contoris.prelievo(50); // metodo di CB

contoris.aggiungiInteresse(); //nuovo CR

contoris.rendiSaldo(); // metodo di CB

Ereditarietà

CB

CR

saldo0 : costruttore

500 : deposito450 : prelievo459 : aggiungiInteresse

tassoInteresse2

contoris

Ereditarietà• Cosa succede quando invochiamoaggiungiInteresse?

contoris.aggiungiInteresse();

• Il codice del metodo è:double interesse =

this.rendiSaldo() *

this.tassoInteresse/100;

this.deposito(interesse);

• I metodi rendiSaldo, deposito e la variabile tassoInteresse sono relativi all’oggetto contoris.

8

Gerarchia di classi

• In Java le classi sono soggette ad una gerarchia: esiste una classe universale di nome Object. (par. 10.2)

Ogni classe che, non derivi direttamente da un’altraclasse, deriva da Object.La classe Object contiene dei metodi che tutte le sottoclassi ereditano.

Object

ContoBancario

ContoRisparmio

Metodi di una sottoclasse

• Quando si definisce una sottoclasse per i suoi metodi ci sono tre possibilità:

1) nella sottoclasse viene definito un metodo nuovo (aggiungiInteresse)2) un metodo della superclasse viene ereditatodalla sottoclasse (rendiSaldo, ...)3) un metodo della superclasse viene sovrascritto nella sottoclasse.

Metodi di una sottoclasse

• Per sovrascrivere un metodo (override) ènecessario definire nella sottoclasse un metodo con la stessa firma: (par. 10.3)

• lo stesso nome

• gli stessi parametri (numero, ordine, tipo) di quelli del metodo della superclasse.

• Il codice invece sarà diverso.

Metodi di una sottoclasse

• Quando il metodo sovrascritto viene invocato da un oggetto di tipo sottoclasse, tale metodo prevale sul metodo della superclasse.

• Supponiamo di voler gestire in CR il fatto che ogni operazione di versamento ha un costo fisso, cioè c’è una tassa (TA) da pagare; tale tassa comporta un prelievo di denaro.

• Dobbiamo modificare in CR il metodo deposito, perché oltre a versare denaro fa anche un prelievo.

Metodi di una sottoclasse

• Come possiamo definire la tassa? Decidiamo che:• sarà una costante: final

• sarà della classe, cioè non legata ad un singolo esemplare: static

private final static double TA = 1.0;

accesso costante di classe tipo nome valore

Metodi di una sottoclasse

• Per sovrascrivere il metodo deposito di CB dobbiamo scrivere un metodo con la stessa firma:

public void deposito(double denaro)

accesso tipo nome parametri

• Per scrivere il codice abbiamo un problema.

9

Metodi di una sottoclasse• Nel nuovo metodo deposito di CR dovremo

invocare il metodo deposito di CB, perché è

l’unico modo per versare denaro.

• Come possiamo fare?

• Soluzione. Il metodo di CB può essere invocato usando il riferimento implicito super, gestito automaticamente dal compilatore, e che permette di accedere agli elementi ereditati dalla superclasse. (par. 10.4)

Metodi di una sottoclasse

//nuovo metodo deposito in CR

public void deposito(double denaro){

//prelevare la tassa

prelievo(TA);

//versare denaro usando deposito di CB

super.deposito(denaro);

}

Metodi di una sottoclasse

• Sintassi.• Per invocare un metodo della superclasse si

utilizza la parola super (riferimento implicito della superclasse)

super.nomemetodo(…)

• È l’unico modo con cui possiamo depositare denaro:

super.deposito(denaro);

Metodi di una sottoclasse

• Potevamo fare diversamente? NO. Infatti:• non possiamo scrivere

deposito(denaro);

perché il metodo deposito diventerebbe ricorsivo (una ricorsione infinita: non c’ècontrollo per il versamento)

• non possiamo agire su saldo perché èprivate

• Il nuovo metodo deposito di CR ha delle sue istruzioni e poi utilizza deposito di CB.

Metodi di una sottoclasse

• Gli oggetti della classe CB usano il metodo deposito di CB; gli oggetti della classe CR usano il metodo deposito di CR.

• Esempio.ContoBancario cb =

new ContoBancario();

ContoRisparmio cr =

new ContoRisparmio(2);

cb.deposito(30); //di CB

cr.deposito(40); //di CR

Metodi di una sottoclasse

• Come e chi si risolve la scelta di quale dei duemetodi deposito deve essere invocato?

• Il compilatore sa quale deposito dovràessere invocato:

cb è di tipo CB cr è di tipo CR

• La distinzione dei due metodi viene fatta tramite il tipo del parametro implicito.

10

Costruttore di una sottoclasse

• Vogliamo ora inserire in CR un secondo costruttore in modo che il saldo abbia un valore iniziale.

• Poiché saldo è private non possiamo usarla direttamente.

• Dato che la variabile saldo è gestita dalla classe CB, bisogna delegare a tale classe il compito di inizializzarla.

Costruttore di una sottoclasse• Nella classe CB avevamo aggiunto un secondo

costruttore per gestire l’apertura di un conto con saldo diverso da zero.

• Invochiamo il secondo costruttore di CB dall’interno del costruttore di CR, usando la sintassi

super(parametri)

La parola chiave super seguita da una coppia di parentesi indica una chiamata al costruttore della superclasse.

Costruttore di una sottoclasse

• Costruzione del secondo costruttore per CR:

public ContoRisparmio(double tasso,

double denaroIniziale){

//invochiamo il costruttore della

// superclasse

super(denaroIniziale);

tassoInteresse = tasso;

}

Costruttore di una sottoclasse• Si sarebbe potuto risolvere il problema

simulando un primo versamento, vale a dire invocando il metodo deposito:

tassoInteresse = tasso;

deposito(denaroIniziale);

• Questo è lecito, ma non è una soluzione molto buona, perché potrebbe introdurre errori:• ad esempio, potremmo volere che le operazioni di

versamento abbiano un costo, sottratto automaticamente dal saldo, mentre il versamento di “apertura conto” sia gratuita.

Costruttore di una sottoclasse

• Quando si utilizza super, questa deve essere la prima istruzione del costruttore: il costruttore di CR per prima cosa invoca il costruttore della superclasse CB, poi esegue le altre istruzioni.

• Come abbiamo fatto con il primo costruttore?• Se non c’è alcuna chiamata esplicita di super, il compilatore considera sottintesa l’invocazione super() senza parametri.

Costruttore di una sottoclasse

• Quando abbiamo considerato il primo costruttore non avevamo inserito alcuna istruzione, ma avremmo potuto mettere

super();

quindi il codice completo sarebbe stato:super();

tassoInteresse = tasso;

• Non avendo inserito nulla l’invocazione di super è sottintesa.

11

Costruttore di una sottoclasse

• Sintassi.

public Sottoclasse (parametri){

/* prima istruzione:

invocazione del costruttore

della superclasse */

super(eventualiparametri)

//altre inizializzazioni

}

Il costruttore predefinito (default)

• Quando viene creato un oggetto (operatore new), per prima cosa viene determinata la classe che rappresenta il tipo dell’oggetto, successivamente viene allocata la memoria (heap) per contenere lo stato dell’oggetto (campi d’esemplare).

• I campi d’esemplare vengono inizializzati:• con le inizializzazioni esplicite, in loro

mancanza con i valori di default,• successivamente viene attivato il costruttore

corrispondente (con o senza parametri).

Il costruttore predefinito (default)

• Se nella classe non c’è alcun costruttore (le variabili d’istanza sono state comunque inizializzate esplicitamente o per default), per poter costruire le istanze della classe viene attivato il costruttore di default: il costruttore predefinito è un costruttore senza parametri, con corpo vuoto. È come scrivere:public Classe(){

//corpo vuoto

}

Il costruttore predefinito (default)

• Pertanto con l’istruzioneClasse varogg = new Classe();

viene costruito un oggetto di tipo Classe

• La funzione del costruttore predefinito èunicamente quella di permettere la costruzionedell’oggetto, in mancanza di costruttori espliciti.

Il costruttore predefinito (default)

• Pertanto, in una classe:• se non c’è alcun costruttore, si attiva il

costruttore predefinito

• se ci sono dei costruttori espliciti, il costruttore predefinito non viene attivato.

• Se si invoca il costruttore di default in presenza di costruttori espliciti (con parametri), il compilatore segna errore.

Il costruttore predefinito (default)• Esempio.

• Costruiamo una classe CB2 simile a CB:

• Il costruttore predefinito non esiste; l’invocazione

CB2 cliente = new CB2();

provoca un errore in compilazione.

public class CB2{ public CB2(double denaroIniziale){

...}// nessun altro costruttore

. . . //metodi come in ContoBancario}

12

Il costruttore predefinito (default)

• Cosa avviene in una sottoclasse di CB2?• Non si può avere il costruttore predefinito,

perché questo invocherebbe super(): il compilatore segnala errore perché manca in CB2 il costruttore senza parametri.

• Non si può fare:public ContoRisparmio(double tasso){

tassoInteresse = tasso;}

perché invoca implicitamente super().

Il costruttore predefinito (default)

• Pertanto, poiché in CB2 non esiste il costruttore senza parametri, tutte le classi derivate da CB2 devono:

• avere almeno un costruttore esplicito

• ogni costruttore deve iniziare con l’istruzionesuper(denaroIniziale);

Il costruttore predefinito (default)

• Una buona scelta è inserire sempre in una classe, in vista di poter costruire delle sottoclassi, un costruttore senza parametri con corpo vuoto, analogo quindi al costruttore predefinito:

public Classe(){//corpo vuoto

}

• In tale modo il costruttore di una sottoclasse potrà invocare super().

Sovrascrivere il metodo toString

Sovrascrivere il metodo toString

• La superclasse Object possiede dei metodi generali, uno dei quali è

toString() (par. 10.8.1)

• Dal momento che Object è la superclasse universale, tutte le classi definite in Java ne ereditano implicitamente i metodi.

• La sua firma è:public String toString()

Sovrascrivere il metodo toString

• Quando questo metodo viene invocato su un oggetto qualunque, restituisce una stringa contenente la cosiddetta “descrizione testuale standard”.

• La stringa è composta dal nome della classe seguito dal carattere @ e dall’indirizzodell’oggetto in memoria.

• Esempio. Sia cb di tipo ContoBancario:String s = cb.toString();

ContoBancario@e7b241

13

Sovrascrivere il metodo toString

• Dal momento che toString restituisce una stringa, si può invocare tale metodo in una istruzione di stampa:

System.out.println(cb);

• Il metodo println invoca cb.toString() e se ne ottiene la descrizione standard.

• Possiamo quindi sovrascrivere toString per farci stampare qualcosa di più significativo.

Sovrascrivere il metodo toString

• Sarebbe molto meglio, per verificare il corretto funzionamento di un programma che usi CB, farci stampare il valore del saldo.

• Questa funzionalità, ovviamente, non può essere svolta dal metodo toString di Object: chi ha definito Object nella libreria standard non aveva alcuna conoscenza della struttura di ContoBancario, né di tutte le altre classi che un utente può realizzare.

• Pertanto vogliamo sovrascrivere toString

in ContoBancario .

Sovrascrivere il metodo toStringpublic class ContoBancario{

. . .

public String toString(){

//sovrascrive il metodo di Object

return " ContoBancario[saldo = "

+ saldo + "] " ;

}//fine toString

}//fine CB

• L’istruzione:System.out.println(cb);

stamperà:ContoBancario[saldo = 1000]

Sovrascrivere il metodo toString

• È bene sovrascrivere il metodo toString in tutte le classi che si definiscono: è considerato un ottimo stile di programmazione.

• Cosa farci restituire come stringa?• Il metodo toString di una classe dovrebbe

produrre una stringa contenente tutte le informazioni sullo stato dell’oggetto:• il valore di tutte le sue variabili di esemplare• il valore di eventuali variabili statiche

Sovrascrivere il metodo toString

• Se vogliamo sovrascrivere toStringaffinché possa essere utilizzato anche dalle sottoclassi, utilizziamo un metodo che ci fornisce il nome della classe:

//in CB

public String toString(){

return getClass().getName() +

"[saldo = " + saldo + "]";

}

Sovrascrivere il metodo toString• Il metodo getClass() appartiene alla classe

Object e restituisce un oggetto di tipo Classche rappresenta la classe dell’oggetto che lo ha invocato. (Argomenti avanzati 10.4)

• Il metodo getName() di Class è un metodo che restituisce il nome della classe.

• Se cr è un oggetto di tipo ContoRisparmio, scrivendo:

System.out.println(cr);

otterremo:ContoRisparmio[saldo = 459]

14

Sovrascrivere il metodo toString

• La classe ContoRisparmio eredita il metodo toString di ContoBancario, ma a sua volta può sovrascriverlo://in CR

public String toString(){

return super.toString() +

"[interesse = "

+ tassoInteresse + " ] ";

}

Sovrascrivere il metodo toString

• Pertanto:System.out.println(cr);

produce:ContoRisparmio[saldo = 459]

[interesse = 2]

• Si può anche sovrascrivere toString in CR senza usare super, ma scrivendo per esteso tutta la stringa e usando rendiSaldo().

Controllo di accesso

Controllo di accesso

• Nel linguaggio Java l’accesso a variabili, metodi e classi può essere: (par.10.7)• public• private• protected• di pacchetto (nessuna specifica)

• public significa che l’accesso è possibile da tutte le classi;

• private significa che l’accesso è possibile solo all’interno della classe;

Controllo di accesso

• protected significa che l’accesso è possibile dalla classe e da tutte le sottoclassi, ed anche dalle classi dello stesso pacchetto;

• di pacchetto significa che l’accesso è possibile all’interno dello stesso pacchetto.

• Come pacchetto possiamo considerare la directory nella quale si trovano le classi: la classe di prova e altre classi. È una specifica buona per classi e metodi ma non per le variabili di istanza che è bene siano sempre private.

Controllo di accesso

• In un pacchetto si inseriscono classi aventi la medesima funzionalità. La prima riga del file che contiene la classe deve iniziare con l’istruzione:

package nomePacchetto;

• Per utilizzare una classe di un pacchetto si può scrivere:

nomePacchetto.nomeClasse

• oppureimport nomePacchetto.nomeClasse;

• Noi non tratteremo la costruzione di pacchetti, ma sarà bene costruire delle cartelle con le classi che risolvono o gestiscono un particolare problema.

15

Variabili statiche o di classe

Variabili statiche o di classe

• Si può avere bisogno di utilizzare una variabile che non sia di un esemplare della classe ma che possa essere condivisa da tutti gli esemplari della classe.

• Esempio. Supponiamo di voler numerare in maniera progressiva i conti correnti in modo tale che ogni correntista abbia un numero di conto diverso; tale numero viene incrementato di uno per ogni nuovo conto attivato: – il primo correntista ha il numero 1, il secondo il

numero 2, ecc.

Variabili statiche o di classe

• Modifichiamo la classe ContoBancario. Questa nuova variabile, numeroConto, rappresenta il numero di correntisti e il suo valore saràl’ultimo numero assegnato.

• Pensiamo alla seguente modifica:public class ContoBancario{

private int ultimoNumero;

private int numeroConto;

public ContoBancario(){

ultimoNumero++;

numeroConto = ultimoNumero;

}//fine costruttore

. . .}//fine CB

Variabili statiche o di classe

• Questo costruttore, però, non funziona perchéla variabile ultimoNumero è una variabile di esemplare: ne esiste una copia per ogni oggetto; il risultato è che tutti i conti creati hanno il numero di conto uguale a 1.

• Ci servirebbe una variabile condivisa da tutti gli oggetti della classe. Una variabile con questa semantica si ottiene con la dichiarazione static:private static int ultimoNumero=0;

Variabili statiche o di classe

• Una variabile static, detta anche variabile di classe, è condivisa da tutti gli oggetti della classe. Di essa ne esiste un’unica copia, ed èper questo che può incrementarsi effettivamente.

• Questa variabile è memorizzata al di fuori degli oggetti CB.

• Ogni oggetto (metodi e costruttori) può accedere a questa variabile e modificarla.

(par. 8.7)

Variabili statiche o di classe

• Per inizializzare una variabile statica non si può utilizzare una istruzione del costruttore, altrimenti il suo valore verrebbe inizializzato ogni volta che si costruisce un nuovo oggetto (e non sarebbe più condivisa).

• Pertanto la si deve:• inizializzare esplicitamente:

private static int ultimoNumero=0;

• oppure, utilizzare l’inizializzazione di default.

16

Variabili statiche o di classe

• L’inizializzazione avviene una sola voltaquando la classe viene attivata.

• Nella programmazione ad oggetti, l’utilizzo di variabili statiche deve essere limitato: infatti i metodi che usano variabili statiche hanno un comportamento che non dipende soltanto dai loro parametri, impliciti ed espliciti.

• Le variabili statiche è bene che siano private, per evitare accessi non controllati.

Costanti statiche o di classe

• È invece comune usare costanti statiche, come nella classe Math:public static final double PI =

3.14159265358979323846;

• Le costanti statiche della classe sono di norma public e per ottenere il loro valore si usa il nome della classe seguito dal punto e dal nome della costante: Math.PI .

TDA: Tipo di dati Astratto

TDA: Tipo di dati Astratto

• Vogliamo rappresentare un nuovo tipo di dato e dobbiamo definire un dominio (i dati, la forma) e le funzioni (metodi) che operano sul dominio (le operazioni che possiamo fare sugli elementi del dominio).

• I tipi di dato astratto (ADT: Abstract Data Type) che andremo a considerare sono “contenitori” di informazioni e si differenziano per le operazioni che possono essere eseguite su quelle informazioni: pila, coda, lista, dizionario.

TDA: Tipo di dati Astratto

• Una volta stabilito cosa può fare il TDA, dobbiamo realizzarlo e scegliere comevengono effettuate le operazioni: dobbiamo scegliere una struttura di dati.

• Una struttura di dati è un modo di organizzare dati ed è caratterizzata da una sua propria modalità di accesso.

• Le strutture di dati che andremo ad utilizzare sono: array e liste concatenate.

TDA: Tipo di dati Astratto

• Ci sono delle operazioni che si fanno in maniera efficiente sia con array che con lista concatenata, altre risultano più complesse con una struttura piuttosto che con l’altra.

• Pertanto volendo realizzare un TDA si dovràanche scegliere la struttura di dati piùidonea.

17

TDA: Tipo di dati Astratto

• Un tipo di dati astratto “contenitore” di informazioni mette a disposizione dei metodi per svolgere le seguenti azioni:• inserimento di un elemento• rimozione di un elemento• ispezione degli elementi contenuti nella

struttura, ricerca di un elemento all’interno della struttura.

Pila o Stack

Pila o Stack

• Una Pila è un TDA ad accesso limitato.• Si può accedere solo al primo elemento della

Pila, detto anche testa.• Le sue funzioni sono:

• verifica se la Pila è vuota

• guarda la testa• inserisci in testa• estrai la testa

Pila o Stack

• In una Pila gli oggetti possono essere inseriti ed estratti secondo un comportamento definito LIFO:

Last In First Outl’ultimo inserito è il primo a uscire

Il nome ricorda una “pila di piatti”:l’unico oggetto che può essere ispezionato è quello che si trova in cima alla pila.

Pila o Stack

• Le operazioni che caratterizzano questo TDA non sono tutte sempre possibili:• possiamo sempre aggiungere un elemento in cima

alla Pila, ma non possiamo togliere un elemento se la Pila è vuota, analogamente non possiamo ispezionare la testa della Pila se la Pila è vuota.

• Vediamo come si realizza una Pila mediante una struttura di dati array e consideriamo un array riempito solo in parte.

Pila o Stack

• L’array è a dimensione fissa; dovremo perciò decidere cosa fare se l’array è pieno: lanciare un’eccezione o eseguire il raddoppio.

• Decidiamo di scegliere il raddoppio dell’array: in tale modo avremo una realizzazione analoga a quella che otterremo usando una lista concatenata.

• Occupiamoci solo del comportamento della Pila: consideriamo una Pila di interi e definiamo il seguente array:

int vett[] = new int [5];

18

Pila o Stack

• Per realizzare il TDA Pila dobbiamo:• rappresentare la situazione

Pila vuota

• sapere quale è laprima posizione libera

(per poter inserire un elemento)

• essere in grado di accedere alla testa

Pila o Stack

• Utilizziamo una variabile intera il cui valore indica la prima posizione libera:

sp : stack pointer• Pila vuota:

sp = 0;• inserire in testa: 4

vett[sp] = valore; 3sp++; 2

• accedere alla testa: 1vett[sp-1] 0

• estrarre la testasp--;

Pila o Stack

• Esempio: • si parte da Pila vuota: sp=0

• inseriamo in testa il valore 6:vett[sp]=6; 4

sp++; // sp=1 3

• inseriamo in testa 15: sp = 2

vett[sp]=15; sp = 1

sp++; // sp=2 sp = 0 6

15

Pila o Stack

• guardiamo la testa: accesso all’elemento

vett[sp-1] // 154

• togliamo la testa: 3sp--; //sp=1 2

sp=1l’elemento non viene sp = 0cancellato, ma non èpiù accessibile

6

15

Pila o Stack

• Cosa c’è nella memoria?

vett

sp

RuntimeStack

1

6

Pila o Stack

• Esempi di utilizzo di una Pila.• Durante l’esecuzione di un programma la JVM

mantiene in uno Stack i descrittori dello stato dei metodi che sono sospesi.

• Un editor mantiene traccia delle operazioni eseguite: quando si effettua un “undo” per annullare un’operazione, si eliminano le ultime eseguite ripristinando lo stato precedente: l’ultima modifica viene eliminata “estraendola” dalla testa della pila.

19

Pila o Stack

• Realizazione del TDA Pila:• dobbiamo scegliere le variabili di istanza:

private int vett[]; //array

private int sp ; //stack pointer

• scriviamo un costruttore:public PilaInteriArray(){

vett = new int[10];

sp = 0;

} //il TDA viene inizializzato vuoto

Pila o Stack

• dobbiamo costruire dei metodi per gli assiomi della Pila: verifica di Pila vuota, guarda la testa, inserisci in testa, estrai la testa:public boolean vuota() {// isEmpty

if (sp==0)

return true;

else

return false;

}

Pila o Stack

//visione della testa:

public int testa() {// top

if (!vuota())

return vett[sp-1];

else

throw new IllegalArgumentException();

/* per ora utilizziamo una eccezione della libreria */

}

Pila o Stack//inserisci in testa

public void inserisci(int elem){

// push

if ( sp == vett.length )

raddoppio();

vett[sp] = elem;

sp++;

//anche vett[sp++] = elem;

}

• Nel caso di array con dimensione fissa, invece del raddoppio, dovremmo sollevare l’eccezione: array pieno

Pila o Stack

// estrai la testa

public void estrai() {// pop

if ( !vuota() )

sp--;

else

throw new IllegalArgumentException();

}

• Nel caso generale, con Object, costruiremo una “nostra” eccezione per i metodi testa ed estrai.

Pila o Stack

• Per poter realizzare il raddoppio abbiamo bisogno di un metodo all’interno della classe; poiché non rientra tra gli assiomi della Pila, possiamo dichiararlo private e sarà visibile solo agli elementi di questa classe:private void raddoppio() {

int tmp[] = new int[2*vett.length];

for(int i=0; i< vett.length ; i++)

tmp[i]=vett[i];

vett = tmp;

}

20

Pila o Stack

• La gestione del nostro array che implementa il TDA Pila è di array riempito solo in parte; non ci interessa, dopo il raddoppio, un successivo ridimensionamento per avere l’array tutto pieno.

• La Pila è costituita dai valori che sono individuati dagli indici:

0, 1, 2, … , sp-1

Stampare una Pila

Stampare una Pila

• Quando si stampa un Pila gli elementi appaiono nell’ordine inverso a quello di inserimento; inoltre la Pila si vuota.

• Supponiamo di avere introdotto nella Pila i valori 1, 2, 3 nell’ordine; per stampare la Pila bisogna accedere ad ogni elemento e poiché èaccessibile solo la testa, per poter “vedere” gli altri elementi si deve togliere la testa. Poichéla testa è l’ultimo elemento inserito, gli elementi stampati appaiono in ordine inverso.

Stampare una Pila

1

3

2

stampa testa stampa testa stampa testa

3 2 1

Pila vuota

Gi elementi stampati sono: 3, 2, 1.

1 1

2

Stampare una Pila

• Se vogliamo stampare gli elementi nello stesso ordine di inserimento, dobbiamo prendere una Pila di appoggio e “rovesciare” quella iniziale.

• Poi stampiamo la nuova Pila.

• Otterremo allora la stampa degli elementi 1, 2, 3.

Stampare una Pila

1

3

2

1

1

2

3

3

2

3

2

1

21

Ricerca in una Pila

• Vogliamo eseguire la ricerca di un elemento in una Pila, ma non ci sono assiomi di “ricerca elemento” tra gli assiomi dello Stack.

• È necessario quindi utilizzare una Pila di appoggio:

• estrarre gli elementi dalla Pila in cui eseguire la ricerca, se la testa coincide con l’elementocercato allora l’elemento è presente; se la Pila iniziale si vuota l’elemento non è presente.

• Successivamente si inseriscono nella Pila iniziale gli elementi tolti.

Pila o Stack

• Esercizio. • Si consideri una formula matematica; scrivere

un algoritmo per verificare se le parentesi sono o no bilanciate.

• Analisi del problema.• La formula

{a + (b-c) * [(a + b) - 7]}ha parentesi bilanciate

Pila o Stack

• La formula{a + (b-c}-5)

non ha parentesi bilanciate, anche se il numero di tonde e graffe aperte coincide con il numero di quelle chiuse. Quindi non è sufficiente contarle.

• Dobbiamo anche considerare il caso di una formula che inizi con una parentesi chiusa.

Pila o Stack

• I soluzione.• Consideriamo un array di caratteri in cui

memorizzare le parentesi. Si esamina la formula un carattere alla volta e si inseriscono le parentesi (aperte) nell’array.

• Quando si incontra una parentesi chiusa si verifica se l’ultima parentesi è la sua corrispondente aperta.

• In questo caso si toglie dall’array la parentesi aperta e si prosegue, esaminando il successivo carattere della formula.

Pila o Stack

• Se alla fine della scansione della formula l’array è vuoto, allora la formula ha parentesi bilanciate.

• Se si incontra una parentesi che non corrisponde oppure se non si vuota l’array, significa che le parentesi non sono bilanciate.

• La gestione dell’array è fatta con un indice che cresce, quando si inseriscono le parentesi, e cala, quando si tolgono.

Pila o Stack

• Osservando gli inserimenti e le cancellazioni delle parentesi, si vede che si tratta della gestione di una Pila.

• II soluzione.• Consideriamo le due stringhe

String aperte = "{[(";

String chiuse = "}])";

• Invece di memorizzare caratteri (le parentesi) memorizziamo gli indici 0, 1, 2 dei caratteri delle due stringhe e consideriamo una Pila di interi.

22

Pila o Stack

• Consideriamo un carattere della formula e vediamo se è un carattere della stringa aperte.

• Se risulta:aperte.indexOf(carattere) ≠≠≠≠ -1

si tratta di una parentesi aperta: possiamo memorizzare il numero corrispondente al valore aperte.indexOf(carattere)nella Pila (e sarà uno di questi: 0, 1, 2).

Pila o Stack

• Se invece risulta: aperte.indexOf(carattere) = -1

• allora il carattere esaminato non è una parentesi aperta, ma sarà un altro carattere oppure una parentesi chiusa.

• Esaminiamo le parentesi chiuse. Se risulta:chiuse.indexOf(carattere) ≠≠≠≠ -1

allora il carattere è una parentesi chiusa.

Pila o Stack

• Guardiamo la testa della Pila: se la testa della Pila ha memorizzato un numero che corrisponde al valore

chiuse.indexOf(carattere)

significa che è memorizzata “la parentesi aperta” corrispondente e pertanto si estrae la testa.

• Se non coincide allora la formula è errata.

• Se lo Stack si vuota allora le parentesi sono bilanciate.

Interfacce

Interfacce

• Quando si tratta un problema, con il termine astrazione si intende l’individuazione delle sue parti fondamentali; a tali parti si attribuisce un nome e se ne indicano le funzionalità(proprietà).

• Vogliamo poter rappresentare una proprietàastratta, senza preoccuparci della sua implementazione: lo scopo è indicare quali sono le proprietà che gli oggetti possiederanno.

Interfacce

• Il linguaggio Java mette a disposizione il concetto di interfaccia (ApplicationProgramming Interface: API).

• Uno degli esempi più importanti dell’utilizzo di interfacce è la definizione di un TDA.

• Vedremo che anche tramite l’uso di interfacce si realizza il riutilizzo del codice.

(Capitolo 9)

23

Interfacce

• Abbiamo detto che uno Stack è un contenitore di informazioni per il quale valgono le seguenti operazioni (proprietà):• isEmpty : verifica di contenitore vuoto• top : visione del primo elemento (testa)• push : inserimento di un elemento in testa• pop : estrazione della testa

• Abbiamo detto che lo Stack si può realizzaremediante:• array• lista concatenata

Interfacce

• Le due realizzazioni dello Stack svolgono gli stessi compiti.

• Vogliamo trovare il modo per poter dichiararequali sono le operazioni (funzionalità) del TDA, senza pensare alla scelta della struttura di dati con la quale si costruirà lo Stack.

• Definiamo il TDA in una interfaccia, dicendo cosa fa.

• Realizziamo il TDA in una classe, dicendo come fa.

Interfacce

• In un’interfaccia si dichiarano i metodi che rappresentano le funzionalità, senza scriverne il codice, che invece appartiene alla realizzazione dell’interfaccia.

• Si indicheranno anche le eventuali eccezioni, per ogni possibile condizione di errore.

• Affinché il TDA sia utilizzabile, si dovràcostruire una classe che lo realizza: nella classe ci sono i metodi concreti, completi del loro codice.

Interfacce

• Sintassi.

public interface NomeInterfaccia{firma dei metodi

}

public class NomeClasse implementsNomeInterfaccia{

tutti i metodi dell’interfaccia: codice relativo all’implementazione scelta

}

Interfacce• Definizione dell’interfaccia Stack:public interface Stack{

boolean isEmpty();

void push(Object elem);

void pop();

Oject top();

}

/* in alcune implementazioni si

definisce

Object pop();

in tale caso il metodo restituisce il valore della testa */

Interfacce• Costruzione dello Stack nella classe:

public class StackArray

implements Stack{

//codice relativo ad uno Stack con

//array

}

public class StackListC

implements Stack{

//codice relativo ad uno Stack

//con lista concatenata

}

24

Interfacce

• Realizzazione del TDA Stack

interfaccia Stack

classi StackArray StackListC

Interfacce

• L’interfaccia è definita con accesso public, perché per sua natura deve poter essere implementata da una classe qualunque.

• I suoi metodi sono automaticamente public(default).

• Quando si realizza in una classe un metodo di un’interfaccia, questo deve essere definito public.

• La classe deve costruire il codice di tutti i metodi definiti nell’interfaccia.

Interfacce

• Differenze tra classe e interfaccia.• Un’interfaccia:

• non ha campi d’esemplare• non ha costruttori• tutti i suoi metodi sono astratti: significa

che del metodo c’è solo la firma (non c’ècodice)

• tutti i metodi sono automaticamente pubblici.

Interfacce

Una classe può estendere una sola altra classe

superclasse

sottoclasse

Interfacce

• Una classe può realizzare più interfacce. • Ogni interfaccia realizza una proprietà e una

classe può voler realizzare varie proprietàdiverse tra loro.

• Vediamo di costruire una classe StackArrayche realizza (implements) l’interfaccia Stack.

• La classe StackArray utilizza un array ridimensionabile di oggetti.

• Dovremo considerare l’eccezione “Stackvuoto” per i metodi pop e top.

Interfaccia Stack e classe StackArray

public class StackArray implements Stack{

private Object vett[]; //array

private int sp ; //stack pointer

public StackArray(){ //costruttore

vett = new Object[2];

sp = 0; }

/** assiomi dello Stack: isEmpty, push, pop, top */

public boolean isEmpty() {

/** verifica di lista vuota: O(1) */

if (sp==0)

return true;

else return false;

}

25

Interfaccia Stack e classe StackArray

public void push(Object elem) {

/**inserimento in testa: in media O(1)*/

if ( sp==vett.length )

raddoppio();

vett[sp] = elem;

sp++; }

public void pop() {

/**estrae il primo: O(1) */

if ( sp > 0 ) // if (!isEmpty())

sp--;

else throw new EmptyStackException("\nStack vuoto: sp = "

+ sp + "\nnon si puo' eseguire pop ");

}

Interfaccia Stack e classe StackArray

public Object top(){

/**visione della testa: O(1)*/

if (isEmpty())

throw new EmptyStackException

("\nStack vuoto: sp = "

+ sp + "\nnon si puo' eseguire top ");

else

return vett[sp-1];

}

/** metodi accessori allo stack */

Interfaccia Stack e classe StackArray

/** restituisce la testa e la toglie */

public Object topAndPop(){//Object pop()

. . . }

private void raddoppio() {

//raddoppio di vett

. . . }

//sovrascrive toString()

public String toString(){

return "nello Stack ci sono " +

sp +" elementi";

}

}//fine classe StackArray

Realizzare Stack

• Le interfacce servono ad esprimere una funzionalità comune a più classi.

• Ad esempio l’interfaccia Stack rappresenta un TDA caratterizzato dalle sue funzioni che saranno comuni alle sue realizzazioni in classi diverse, su array o lista concatenata.

• Per vedere la realizzazione dell’interfaccia Stack, dopo aver costruito la classe StackArraydobbiamo costruire una classe di prova per StackArray.

Realizzare Stack

• Nella classe di prova andiamo a costruire un oggetto di tipo StackArray sul quale applicare i metodi dello Stack.

• Abbiamo due possibilità: 1) la solita costruzione di un oggetto di tipo NomeClasseStackArray stA =

new StackArray();

Realizzare Stack

2) definire un “tipo interfaccia” e costruire l’oggetto specificando la classe:

Stack st = new StackArray();

In questo secondo caso abbiamo una conversione di tipo tra classe e interfaccia.

26

Realizzare Stack

• Nel secondo caso l’operatore new costruisce un oggetto di tipo StackArray e restituisce un riferimento a un oggetto di tipo StackArray, che può essere assegnato a st di tipo Stacksolo perché StackArray implementaStack (altrimenti il compilatore segnalerebbe errore).

• Tramite il riferimento st possiamo usare tutti i metodi dell’interfaccia Stack.

• Non possiamo usare topAndPop che è solo della classe StackArray.

Realizzare Stack

• Per capire la potenza della definizione di TDA come interfaccia, supponiamo che qualcuno abbia progettato la seguente classe:public class StackX implements

Stack{

...//codice sconosciuto

}

• Anche senza sapere come sia stata realizzata StackX, possiamo usarne un esemplaremediante il suo comportamento astratto definito in Stack.

Realizzare Stack

• Possiamo usare un esemplare attraverso i metodi: isEmpty, top, pop, push.

• Possiamo usare un esemplare di qualsiasiclasse, StackY, che realizza Stack.

• Esempio. • Costruiamo una classe di prova che costruisce

uno Stack inserendo oggetti di tipo stringa e poi stampiamo lo Stack.

Realizzare Stack

• Nella classe di prova possiamo scrivere:Stack s = new StackX(); //new StackY();

s.push("Pippo");

s.push("Pluto");

s.push("Paperino");

//stampa dello Stack

while(!s.isEmpty()){

System.out.println(s.top());

s.pop();

}

• ottenendo la costruzione dello Stack, la sua stampa e lo svuotamento dello Stack.

Costruire un’eccezione

• Nella classe StackArray si gestisce la situazione “Stack vuoto” con il lancio di un’eccezione:

EmptyStackException

• Costruiamo quindi la nostra nuova eccezione.• Dobbiamo utilizzare un modo per eseguire

l’interruzione del programma, quindi costruire una classe che sia sottoclasse di una “classe eccezione”. (par. 11.6)

Costruire un’eccezione

• Dobbiamo fare delle scelte.

• I scelta. Stabilire se l’eccezione estenderà le RuntimeException oppure estenderàException.

• II scelta. Vogliamo utilizzare i costruttoridell’eccezione o gestire un messaggio personale che spieghi il lancio dell’eccezione.

27

Costruire un’eccezione

• I scelta.• Le eccezioni che estendono le

RuntimeException non sono a controllo obbligatorio.

• Le eccezioni che estendono Exception sono a controllo obbligatorio e pertanto in tutti i metodi che sono coinvolti nel lancio dell’eccezione si deve scrivere nella firma:

throws EmptyStackException

I metodi sono: pop, top, topAndPop, main.

Costruire un’eccezione

• II scelta.• Utilizziamo costruttori e metodi di Exception:

public class EmptyStackException

extends Exception {

//corpo vuoto

}

• Con questa scelta la classe solamente ereditail comportamento (metodi e costruttori) di Exception: abbiamo solo costruito una classe con un nome che ricorda il tipo di problema.

Costruire un’eccezione

• Vogliamo poter introdurre dei nostri messaggi per stampare informazioni sullo stato dell’oggetto.

• Nel metodo top di StackArray abbiamo inserito un messaggio in cui visualizzare il valore di sp:if (isEmpty())

throw new EmptyStackException

("\n Stack vuoto: sp = "

+ sp + "\n non si puo' eseguire top ");

Costruire un’eccezione

• Dobbiamo pertanto scrivere i costruttori. Solitamente in una classe che costruisce un’eccezione si hanno due costruttori:• uno senza parametri• uno con parametro di tipo stringa per

visualizzare un messaggio.• Costruiamo una classe che estende le

RuntimeException; se invece la volessimo a controllo obbligatorio, nell’intestazione del metodo top dovremmo scrivere

throws EmptyStackException

Costruire un’eccezionepublic class EmptyStackException extends

RuntimeException {

//primo costruttore

public EmptyStackException(){

//corpo vuoto

}

//secondo costruttore

public EmptyStackException

(String messaggio){

super(messaggio);

//richiama il metodo getMessage

//della superclasse

}

}//fine classe