Paola Magillo, Univestita' di Genova, Corso di Programmazione II per SMID, a.a. 2004-2005.

Lezione 13:

PERSISTENZA

Oggetti persistenti = Oggetti che sono capaci a scriversi su file e rileggersi da file. Servono per "ricordare" lo stato di un oggetto tra una esecuzione e l'altra del programma.

Classi persistenti implementano l'interfaccia Persistent, che non ha metodi, ma occorre dichiare che una classe implementa Persistent per poterla scrivere e rileggere attraverso i metodi di ObjectOutputStream e ObjectInputStream.

ObjectOutputStream

Classe le cui istanze rappresentano file e sono in grado di scrivere oggetti appartenenti a classi che implementano l'interfaccia Persistent.

Costruzione di un'istanza di ObjectOutputStream, per es. sia "pippo.txt" il nome del file, e scrittura di un oggetto sul file:

 FileOutputStream fos = new FileOutputStream("pippo.txt");
 ObjectOutputStream oos = new ObjectOutputStream(fos);
 oos.writeObject(...oggetto da scrivere...);
 oos.close();
 fos.close();
Nota: tutte queste operazioni possono sollevare eccezioni di classe IOException.

ObjectInputStream

Classe le cui istanze rappresentano file e sono in grado di leggere oggetti appartenenti a classi che implementano l'interfaccia Persistent.

Costruzione di un'istanza di ObjectInputStream, per es. sia "pippo.txt" il nome del file, e lettura di un oggetto dal file:

 FileInStream fis = new FileInputStream("pippo.txt");
 ObjectInputStream ois = new ObjectInputStream(fos);
 ...oggetto da leggere... = (...classe dell'oggetto...)ois.readObject();
 ois.close();
 fis.close();
Note:
L'oggetto viene restituito dalla funzione read come oggetto di classe generica Object, bisogna convertirlo forzatamente alla sua classe effettiva (fare un cast).
Tutte queste operazioni possono sollevare eccezioni di classe IOException.

Esempio: Financial history

Tiene traccia dei movimenti su un conto bancario. I suoi attributi sono:

Ciascuna entrata / uscita e' una coppia (c,s) dove c = la cifra ed s = una stringa di descrizione.

Entrate e uscite sono un insiemi di coppie. Potremmo definire noi una classe per questi insiemi, invece usiamo classi predefinite di Java.

Classe Hashtable

Una tabella hash (hash table) e' un insieme di associazioni chiave ---> valore.
Nel nostro caso

Se un valore v e' stato inserito nella tabella con chiave k, viene anche ritrovato dalla tabella fornendo la chiave k (chiave = indirizzo, valore = contenuto).

Chiavi e valori sono genericamente oggetti (classe Java Object).
Essendo nel nostro caso il valore un numero intero (che e' tipo semplice e non classe), dobbiamo usare la sua classe wrapper Integer (ved. lezione 5).

La tabella hash e' un array con un certo numero di caselle ciascuna delle quali ha associata una lista di valori, chiamata bucket (= secchio).
Ad ogni chiave corrisponde una casella dell'array, la posizione di questa casella e' data da una funzione di hash (in Java la classe Object implementa il metodo int hashCode() che ritorna il valore della funzione di hash per l'oggetto). Ci sono piu' chiavi che caselle nell'array, percio' piu' chiavi finiscono alla stessa casella.
Un valore v inserito nella tabella hash con chiave k viene messo nel bucket della casella corrispondente alla chiave k. Assieme a v viene messa nel bucket anche la sua chiave (perche' lo stesso bucket corrisponde a piu' chiavi).
Fornendo poi la chiave k e' possibile ritrovare il valore andandolo a cercare nel bucket.

Un'instanza di Hashtable ha due parametri: capacita' e fattore di carico. La capacita' e' il numero di caselle nell'array (quanti bucket). Il fattore di carico stabilisce un limite alla lunghezza media dei bucket. Quando il numero di elementi eccede il prodotto tra capacita' e fattore di carico (cioe' quando la lunghezza media dei bucket eccede il fattore di carico), la tabella intera viene automaticamente ricostruita con una capacita' maggiore.

Costruttori:

Funzioni per inserire e ritrovare elementi:

Esempio:
 Hashtable numbers = new Hashtable();
 numbers.put("one", new Integer(1));
 numbers.put("two", new Integer(2));
 numbers.put("three", new Integer(3));
 Integer n = (Integer)numbers.get("two");
 if (n != null) System.out.println("two = " + n);

Interfaccia Enumeration

L'idea e' quella di uno scatolone da cui possiamo tirare fuori tutti gli elementi uno alla volta.

Metodi:

Per esempio, per tirare fuori tutte le coppie presenti in una tabella hash ht:

  Enumeration en = ht.keys();
  while (en.hasMoreElements())
  {  Object k = en.nextElement()); // chiave
     Object v = ht.get(k); // valore
     ...fare qualcosa con la coppia (k,v)...
  }

Classe FinancialHistory

File FinancialHistory.java. Implementa l'interfaccia Serializable, quindi puo' essere scritta e riletta da file.

Attributi (privati):

Nello stesso file sono definite due classi per eccezioni: NegAmountException e NegCashException.

Costruttore della classe FinancialHistory:

Metodi che leggono la situazione del conto:

Nota: l'implementazione di receivedFor e spentFor interroga la tabella hash, che ritorna un oggetto, tale oggetto viene convertito forzatamente a Integer, infine ne viene preso il valore int.

Metodi che modificano la situazione del conto:

Nota: entrambe le funzioni vogliono amount > = 0 altrimenti sollevano eccezione NegAmountException; la funzione spendFor controlla anche che dopo la spesa il conto non vada in rosso, e nel caso solleva eccezione NegCashException.

Metodi per stampa:

Per costruire la stringa risultato si avvalgono di un oggetto di classe StringBuffer (stringa modificabile).

FinancialApplet

File FinancialApplet.java. Interfaccia grafica per FinancialHistory. Puo' funzionare sia come applet (e' sottoclasse di Applet) che come applicazione (ha un main che mette l'applet in un frame).

Ha tra i suoi attributi un oggetto history di classe FinancialHistory.

L'applet contiene tre bottoni:

Ogni bottone fa apparire una finestra di dialogo. Un'altra finestra di dialogo appare per segnalare errori (le varie eccezioni che possono sorgere).

Le varie finestre di dialogo sono realizzate con classi a parte.

Classi per le finestre di dialogo

Dialog1 -- Definisce l'organizzazione comune a tutte le finestre di dialogo.

La finestra e' strutturata con BorderLayout, a sud ha un pannello per bottoni (con FlowLayout), al centro e' predisposta per accogliere un altro pannello.
Il pannello a sud per adesso ha solo il bottone okButton e il metodo addOkListener permette di aggiungere un comportamento a questo bottone.
Il centro per adesso e' vuoto ma il metodo addCenter consente di riempirlo con un pannello.

Le finestre per situazione ed errori sono di classe Dialog1:

Questi pannelli vengono costruiti e aggiunti al volo con addCenter subito prima di mostrare la finestra (ved. metodi prepareErrorDialog e prepareInfoDialog della classe FinancialApplet).

Dialog3 -- Definsice lo schema comune delle finestre di deposito e prelievo.

Estende Dialog1:

Gestione degli eventi in FinancialApplet

Una serie di classi interne a FinancialApplet implementa gli event listener per le varie operazioni.

ShowListener -- comportamento per mostrare una finestra di dialogo

HideListener -- comportamento per nascondere una finestra di dialogo

ClearListener -- comportamento di azzeramento dei campi di una finestra di classe Dialog3

DoDeposit -- comportamento per l'operazione di deposito

DoWithdraw -- comportamento per l'operazione di prelievo

Funzioni per preparare i dialoghi

La funzione init di FinancialApplet

Sostituisce costruttore e main nel caso in cui il programma gira come applet.

Costruisce tutte le finestre di dialogo (modali), costruisce i bottoni dell'applet e stabilisce le callback dei bottoni. Vediamo piu' in dettaglio...

Crea finestra di dialogo per situazione (infoDialog) di classe Dialog1. Stabilisce comportamento per il bottone okButton (nascondera' la finestra, action listener di classe HideListener).

Crea finestra di dialogo per deposito (depositDialog) di classe Dialog3. Stabilisce comportamento per il bottone okButton (eseguira' il deposito, action listener di classe DoDeposit), per il bottone clearButton (azzerera' i campi, action listener di classe ClearListener) e dismissButton (nascondera' la finestra, action listener di classe HideListener).

Crea finestra di dialogo per prelievo (withdrawDialog) di classe Dialog3. Analogo a depositDialog, ma il bottone ok eseguira' il prelievo (action listener di classe DoWithdraw).

Crea finestra di dialogo per errori (errorDialog) di classe Dialog1. Stabilisce comportamento per il bottone okButton (nascondera' la finestra, action listener di classe HideListener).

Poiche' una finestra di dialogo deve dipendere da un frame, la funzione getFrame cerca, risalendo nella gerarchia di contenimento delle finestre, il frame che contiene l'applet (un frame che lo contiene esiste anche quando gira nella pagina web...).

Stabilisce che l'applet e' gestito con FlowLayout, crea e aggiunge i tre bottoni e ne stabilisce il comportamento: mostreranno le tre finestre di dialogo per deposito, prelievo, situazione (action listener di classe ShowListener).

Il main di FinancialApplet

Se il programma funziona come applicazione, entrano in gioco i costruttori e il main.

Costruttori:

Sono due, uno prende in argomento un nome di file da cui leggere la financial history, l'altro senza argomenti crea da zero una financial history con cifra iniziale uguale a 1000.

Metodo main:

Crea un frame dove mettere l'applet, aggiunge la gestione della chiusura del frame, mostra il frame.
Se da command-line e' dato un nome di file, l'applet viene creato col costruttore nella forma che la legge history da file.
Alla chiusura del frame, se history era stata letta da file la riscrive sullo stesso file.