Problemi per la lezione del 09/01
L'obiettivo di questa esercitazione è duplice: da un lato, acquisirete dimestichezza con alcune classi di I/O standard in Java;
dall'altro, vi troverete a dover gestire eccezioni (controllate) e dovrete ogni volta stabilire se farlo mediante il costrutto try/catch
o sollevando a vostra volta l'eccezione al chiamante (mediante la clausola throws).
- [Uso di stream di dati] L'obiettivo di questo esercizio è di scrivere su un file, in formato binario,
dei dati. Per farlo useremo le classi FileOutputStream e FileInputStream (che, rispettivamente,
servono per scrivere e leggere file visti come flussi di byte) e gli adattatori DataOutputStream
e DataInputStream (che permettono di scrivere e leggere tutti i tipi primitivi e anche gli oggetti
serializzabili).
Scrivete un programma (costituito dal solo metodo main) che operi come segue:
- Aprite un nuovo file (in scrittura) e avvolgetelo in un DataOutputStream. Abbiate cura di
fornire il pathname completo del file in modo da essere sicuri di dove il file verrà creato.
- Su questo DataOutputStream scrivete, ad esempio, in questo ordine: l'intero 5, il booleano true,
il numero double Math.PI, l'intero 14 e l'intero 27.
- Chiudete lo stream ed eseguite il programma. Potrete verificare che:
- il file è stato creato;
- la lunghezza in byte del file è quella che vi aspettate? (Ricordatevi che un intero occupa 4 byte, un booleano
1 byte e un double 8 byte);
- se provate ad aprire il file con un editor (o a fare un cat del file), che cosa vedete?
Ora scrivete un altro programma (costituito dal solo metodo main) che operi come segue:
- Aprite lo stesso file di prima (in lettura) e avvolgetelo in un DataInputStream.
- Leggete dall'input stream un intero, un booleano, un double e due interi.
- Stampate i dati appena letti (usando System.out.println) e verificate che siano gli stessi che avevate scritto.
- Che cosa succede se leggete gli stessi dati ma in un ordine diverso (ad esempio tre interi, un double e un booleano)?
- [Apertura in append] Un FileOutputStream si può aprire in append: ciò vuol dire che il
file, se esiste già, non viene cancellato ma i dati che scriverete verranno scritti in fondo a quelli già presenti.
Cercate il costruttore che vi permette di aprire un output stream in append; ora modificate il programma di scrittura
dell'esercizio precedente in modo che, dopo aver chiuso il flusso, lo riapra in append e scriva altri dati a vostro piacimento.
Modificate il programma che legge il file aggiungendo le letture dei dati in più.
- [Uso di un Writer] Scrivete un programma che apra in scrittura un FileOutputStream
e avvolgete l'output stream in un OutputStreamWriter che converte l'output stream (flusso di byte) in un flusso di
caratteri. Notate che il costruttore vuole, oltre all'output stream su cui scrivere, anche la codifica da usare per convertire i
caratteri in byte: usate la codifica "ISO-8859-1".
Ora, usando il metodo write(String s) del writer che avete creato, scrivete sul flusso delle stringhe a vostro
piacimento e chiudete il writer.
Da un terminale provate a fare un cat del file e verificare che cosa avete prodotto.
- [Uso di un PrintWriter] Modificate il programma precedente come segue: dopo aver creato
l'OutputStreamWriter, avvolgetelo in un PrintWriter.
Il PrintWriter ha dei metodi (di nome print e println) che consentono di stampare
qualunque cosa (tipi primitivi e oggetti; per questi ultimi verrà invocato il loro metodo toString)
su un writer. Inoltre c'è un metodo printf che funziona come la funzione omonima del C. Ecco un esempio d'uso
(pw è un PrintWriter):
pw.prinf("Se x=%.2f e y=%.2f allora si ottiene %.4f come risultato!\n", x, y, Math.sqrt(x*x+y*y));
Usate il PrintWriter per stampare sul flusso quel che volete. Verificate il risultato ottenuto facendo
un cat del file.
- [Lettura di un file di testo, riga per riga] Il nostro obiettivo è leggere il file che avete appena scritto,
una riga per volta. Per farlo, create un FileInputStream e avvolgetelo in un InputStreamReader (dovrete
ovviamente specificare la codifica, come prima). Ora avvolgete quest'ultimo in un BufferedReader: quest'ultimo
fornisce un metodo readLine() che legge e restituisce un'intera riga (fino all'a-capo), oppure
null
quando il flusso è finito. Scrivete un ciclo che legga una riga per volta il file fino alla fine (potete usare un ciclo infinito
in cui fate una readLine() e un break non appena quest'ultima restituisce null
), stampando ogni
volta sullo schermo la riga letta. Verificate che quel che ottenete sia quello che avevate visto con il cat.
- [Uso di Scanner] Create un file (come spiegato nell'esercizio sui PrintWriter) e scrivete
sul file, una linea per volta, due interi, un double e un altro intero a vostro piacimento. Controllate il contenuto del file.
Ora create un FileInputStream e avvolgetelo in un InputStreamReader e avvolgete quest'ultimo in uno Scanner: questo
fornisce metodi quali nextInt() e nextDouble() che leggono e restituiscono un intero o un double dal flusso.
Usate questi metodi per leggere due interi, un double e un ultimo intero, e verificate che i dati letti siano quelli che vi aspettate.
- [Comportamenti inattesi di Scanner] Quest'ultimo esercizio serve per mettervi in guardia da un comportamento
inatteso che accade quando usate uno scanner per leggere dati e linee. Modificate il programma dell'esercizio precedente in modo
che dopo il double e prima dell'intero venga stampata una linea contenente una stringa qualsiasi.
Ora modificate analogamente la lettura invocando, dopo il metodo nextDouble e prima dell'ultima chiamata a nextInt, il metodo nextLine
che dovrebbe leggere un'intera riga. Che cosa accade?
La spiegazione è che i metodi nextInt, nextDouble ecc. non consumano mai nessun carattere in più oltre a quelli previsti. Più precisamente,
sono in grado di saltare eventuali spazi prima dell'intero (o del double) da leggere, ma dopo aver letto tale intero/double/ecc. non consumano nessun
altro carattere. Quindi, dopo aver letto il double, il flusso rimasto disponibile inizia con un "a-capo" non consumato (quello che segue immediatamente
il double). Per risolvere il problema, aggiungete una chiamata a nextLine che consumi l'a-capo in più.
- [Classe Orario] Implementate una classe di nome Orario che rappresenta un orario, specificato
come ore e minuti. Internamente, la classe deve mantenere un singolo attributo intero (privato) che rappresenta i minuti trascorsi dalla mezzanotte. (In realtà,
per la realizzazione degli ultimi due metodi indicati sarà necessario aggiungere altri attributi, il cui tipo e significato sono soggetti a vostra valutazione)
La classe deve avere i seguenti costruttori e metodi:
- Orario(int h, int m): costruisce l'orario h:m (h deve essere compreso fra 0 e 23, e m deve essere compreso fra 0 e 59; decidete
come comportarvi se queste condizioni non si verificano);
- Orario(int h): costruisce l'orario h:00
- Orario(): costruisce l'orario corrispondente all'ora attuale; per ottenere tale orario, tenete conto che:
- l'ora attuale si può ottenere con l'istruzione (new GregorianCalendar()).get(Calendar.HOUR_OF_DAY)
- i minuti attuali si possono ottenere con l'istruzione (new GregorianCalendar()).get(Calendar.MINUTE)
- int getOre(): restituisce le ore
- int getMinuti(): restituisce i minuti
- void setOre(int h): cambia le ore, lasciando i minuti invariati
- void setMinuti(int m): cambia i minuti, lasciando le ore invariate
- void setOreMinuti(int h, int m): cambia sia le ore che i minuti
- boolean equals(Orario x): restituisce true sse l'orario su cui è invocato è uguale a x
- int compareTo(Orario x): restituisce un valore negativo se l'orario su cui è invocato viene prima di x, 0 se i due
orari sono uguali, e un valore maggiore di zero se l'orario su cui è invcoato viene dopo
- int quantoManca(Orario x): restituisce il numero di minuti dall'orario target all'orario x (sarà un valore negativo
se x viene prima dell'orario attuale)
- String toString(): restituisce una stringa della forma h:m
- void setSeparatore(char separatore): permette di scegliere con che separatore stampare l'orario (da ora in
avanti verrà usato come separatore il carattere indicato, anziché nel metodo toString())
- void setFormato(boolean formato24): permette di scegliere se stampare l'orario (da ora in poi) nel formato
24 ore, oppure nel formato 12 ore: in questo caso, il metodo toString() dovrà restituire ore della forma
"h:m AM" oppure "h:m PM" (al posto dei : ovviamente dovrete usare il separatore specificato).
Sperimentate la classe, realizzando un programma di test.