Sei sulla pagina 1di 136

Programmare e Progettare

con Java.
Lezione 3 - Tipi Generici, Collezioni e IO
TIPI GENERICI
UN PROBLEMA GENERICO
Spesso si desidera costruire classi che possano gestire diversi tipi di oggetti non
accomunati da alcun tipo di gerarchia se non avendo Object come radice.

Prima soluzione: Dichiarare un campo di tipo Object così che esso possa essere
occupato da qualsiasi tipo di oggetto. Problema: nulla impedisce di inserire oggetti
inaspettati causando errori quando un cast incontra un tipo inaspettato.

Seconda soluzione: Creare diverse classi che gestiscono diversi tipi di oggetto.
Problema: Approccio tedioso, spesso insufficiente e incline all’errore.

Entrano in gioco i tipi generici.


CLASSE SENZA TIPI GENERICI
CLASSE CON TIPI GENERICI
PRIME OSSERVAZIONI
La scrittura Cell<E> si legge come “Cell di E” e rappresenta il tipo di elemento che un
oggetto Cell può contenere.
Il nome E viene usato in tutti i punti dove era usato Object ed è noto come variabile di
tipo: ed essa si può sostituire un tipo concreto.
Scelta del nome basata su convenzioni - un carattere e identifica la tipologia di oggetti
trattabili:
★ E Tipo di elemento
★ K Tipo di chiave
★ V Tipo di valore
★ T Tipo generale
★ Altri...
CREARE OGGETTI CON TIPO
Per creare un oggetto di tipo Cell bisogna istruire il compilatore: che tipo vogliamo
sostituire al nome E? Se Cell dovesse contenere una stringa, allora:

Cell<String> strCell = new Cell<String>(“Hello”);

Tale informazione va data:

➔ Nella dichiarazione della variabile


➔ Nel costruttore

Classi che utilizzano la classe che usa questa tecnica dovranno essere dichiarate
in simil modo.
DICHIARAZIONE DI TIPI GENERICI
❖ class nomeClasse<T> Dichiarazione tipo generico
❖ E Tipo Parametro
❖ nomeClasse<String> Tipo Parametrizzato (<Tipo Argomento>)
❖ nomeClasse Tipo Grezzo
Usare un Tipo Parametrizzato è un’operazione comunemente nota come
invocazione del tipo generico e specifica al compilatore che si sta usando il Tipo
Grezzo parametrizzato secondo il Tipo Argomento: nessuna nuova classe viene
creata!
ES: Class<String> e Class<Integer> sono la stessa classe invocata tramite diversi
Tipi Argomento. La classe è Cell e il file in cui va salvata è Cell.java!
ESEMPIO DI USO DEI TIPI ARGOMENTO

● Scrivere la classe Cell<E> e dotarla di un main che esegua il codice sopra


riportato e stampare a schermo il valore di ‘same’.
UNA CLASSE, DIVERSE CONSEGUENZE
➔ Non si possono dichiarare campi o metodi statici che utilizzino il tipo
parametro!
➔ Un tipo parametro può apparire in qualunque dichiarazione non statica nel
punto in cui normalmente si collocherebbe un tipo concreto.
➔ Non è possibile istanziare il tipo parametro, né è possibile creare array del
tipo parametro.
ESERCIZI
● Rivedere la classe LinkedList e riscriverla sotto forma di classe generica.
● Rivedere la classe Attributo e riscriverla sotto forma di classe generica.
INTERFACCE GENERICHE
Negli esempi visti, al posto di E si sarebbe potuto utilizzare un qualsiasi tipo
riferimento. Raramente si vorrà che la classe possa essere utilizzata senza limiti
sul tipo argomento.
ES: Una collezione ordinata può contenere un qualsiasi tipo di oggetto, basta che
esso sia ordinabile.
Per definire un oggetto con una simile proprietà si usa Comparable: essa è
un’interfaccia generica. Una classe che la implementa viene infatti dichiarata
come:
class nomeClasse implements Comparable<nomeClasse>{...}
TIPI PARAMETRO VINCOLATI
Un’interfaccia può restringere il suo tipo parametro a una ristretta cerchia di tipi.

interface CollezioneOrdinata<E extends Comparable<E>> {...}

E è ristretto ad un tipo che estende Comparable<E>: esso deve supportare i metodi


dell’interfaccia Comparable!

In questo caso Comparable è un vincolo superiore al tipo di E, mentre E è un tipo vincolato.

Nota: Una classe può usare extends per estendere una sola altra classe, mentre più interfacce
possono essere estese da una classe o implementate da interfacce. Il vincolo di tipo può
esprimere tali dipendenze multiple dichiarando la sequenza interlacciata da &. Ad ES:

interface CollezioneOrdinataConCharSeq<E extends Comparable<E> & CharSequence>


SOTTOTIPI E SEGNAPOSTO
Sappiamo che Integer è un sottotipo di Number e che Integer[] è un sottotipo di
Number[].

nomeClasse<Integer> è un sottotipo di nomeClasse<Number>? Ebbene, no.

Bisogna dichiarare liste di elementi che siano compatibili con Number se si vuole
ottenere un effetto simile: il segnaposto ‘?’ seguito dalla keyword extends seguita
dal tipo che si vuole considerare supertipo. Quest’ultimo è chiamato vincolo
superiore.
ESEMPIO D’USO
SUPER SEGNAPOSTO
È possibile usare i segnaposto per comunicare al compilatore che si possono
usare anche supertipi del tipo indicato, oltre che il tipo indicato.

Questo è il lavoro dei super segnaposto:

nomeClasse<? super tipo>

In questo caso, tipo è chiamato vincolo inferiore.

ES: <? super Integer> può corrispondere a <Integer>, <Number>, <Serializable>,


<Comparable<Integer>>, <Object>.
SEGNAPOSTO NON VINCOLATI
nomeClasse<?>

Il ‘?’ in questo caso rappresenta un qualsiasi tipo, ma non è equiparabile a


nomeClasse<Object>: è equivalente a nomeClasse<? extends Object>

Importante visualizzare l’ereditarietà dei tipi:


LIMITI DEI SEGNAPOSTO
I segnaposto rappresentano tipi sconosciuti e quindi non possono essere usati per
fare tutte quelle operazioni che richiedono che il tipo sia noto.

I segnaposto possono essere utilizzati nella maggior parte delle dichiarazioni


(campi, variabili locali, tipo dei parametri, tipi restituiti) ma non possono essere
utilizzati per nominare una classe o interfaccia in una clausola extends o
implements.
PROGRAMMA DI RIFERIMENTO
COSTRUTTORI GENERICI
Un costruttore può essere generico anche se la classe non la è: questo viene
fatto per poter fare in modo che l’oggetto venga costruito a partire da un qualsiasi
tipo che abbia però dei limiti.

Il tipo generico introdotto è posizionato tra i modificatori e il nome del costruttore.

ES:
METODI GENERICI
Similmente ai costruttori, anche i metodi possono introdurre tipi generici,
dichiarandoli tra i modificatori ed il tipo restituito come:

modificatori tipoGenerico tipoResituito nomeMetodo(Parametri){...}

T può essere un qualsiasi oggetto che estende il concetto di Bottiglia, ovverosia


un qualsiasi oggetto che implementi l’interfaccia Bevanda.
ERASURE
Il procedimento di erasure consente al compilatore di comprendere quale sia il
tipo specifico del tipo generico e consiste nel cancellare tutte le informazioni
relative alla parte generica del tipo: si arriva al tipo grezzo (per una classe
Bevanda<Acqua> o Bevanda<Vino> il tipo grezzo è Bevanda).

La erasure di una variabile di tipo è quella del suo vincolo superiore:

ES:

➔ Per E all’interno di <E> è object


➔ Per E all’interno di <E extends Number> è Number
ERASURE IN ESECUZIONE
Nulla che richieda la conoscenza del tipo argomento è possibile in esecuzione:

❏ Impossibile istanziare (new T()) e dichiarare array di tale tipo (new T[n])
❏ No array il cui tipo di elementi è il tipo parametrizzato (new
Bevanda<Acqua>[1]), eccetto esso è tale che tutti gli argomenti sono di tipo
segnaposto non vincolato (new Bevanda<?>[1])
❏ Una classe generica non può estendere Throwable: le eccezioni generiche
non sono intercettabili.
OVERLOADING E OVERRIDING
Nuova definizione: stessa segnatura per due metodi generici indica che hanno lo
stesso numero di variabili di tipo dotate degli stessi vincoli. Inoltre, rinominando
tutte le occorrenze delle variabili di tipo del secondo metodo con le corrispondenti
del primo, i tipi dei parametri formali devono coincidere.

Due segnature sono equivalenti sotto il punto di vista dell’overriding se le loro


segnature o le erasure di esse coincidono.

Due metodi generici sono sovraccaricati se hanno lo stesso nome e non hanno
segnature overriding-equivalent. In caso contrario si ha un errore!
ESEMPIO OVERLOADING
OVERLOADING ERRATI
Aggiungere metodi generici la cui erasure è uguale a quella dei metodi dichiarati
genera errori. Ad ES:

❖ void m(Object o) { }
❖ void m(Number n) { }
❖ <G extends String> void m(G g) { }
❖ void m(Bevanda<Object> q) { }
OVERRIDING
Un metodo interno ad un sottotipo ridefinisce potenzialmente un metodo
accessibile all’interno del supertipo se i due metodi hanno ugual nome e sono
override-equivalent. La segnatura del metodo della sottoclasse deve essere la
stessa di quella del metodo della superclasse o della sua erasure.

Overriding a senso unico: un metodo senza tipo generici può ridefinirne uno con,
ma non viceversa!
ESEMPIO OVERRIDE
EURISTICA GENERALE
Quando si pensa di fare un overload o un override bisogna sempre tenere a
mente la erasure delle segnature dei metodi e nel ricordare che non è possibile
aumentare la genericità di un metodo ereditato.
ESTENDERE CLASSI E TIPI GENERICI
★ Si può estendere un tipo non generico per produrre un sottotipo che può essere sia
generico che non.
★ Si può estendere un tipo generico per produrre un sottotipo generico.
★ Si può estendere un tipo parametrizzato particolare per produrre un sottotipo non
generico.
★ Si possono combinare le penultime due opzioni.
Attenzione quando si estendono tipi parametrizzati! Valutare le conseguenze, in
particolar modo se si implementano interfacce poiché esse non possono avere
parametrizzazioni diverse!
ES: Se ad un certo punto definisco la classe Value e implemento Comparable<Value>,
poi non posso implementare Comparable<ExtendedValue> in una classe ExtendedValue
che estende Value!
COLLEZIONI
COLLECTION FRAMEWORK
Collection è un framework (insieme di strumenti SW da cui l’utente può partire
per creare programmi) di Java che fornisce un’architettura adatta ad
immagazzinare e gestire gruppi di oggetti, realizzando di fatto delle strutture dati.
Le maggiori operazioni fornite da questo framework sono:
➢ Ricerca
➢ Ordinamento
➢ Inserimento
➢ Modifica
➢ Eliminazione
Vedremo interfacce e classi fornite.
STRUTTURA DI COLLECTION
ARRAYLIST
ArrayList è una collezione che funziona in modo simile ad un array, ma a
differenza di questi ultimi essa è dinamica: può avere un numero indefinito di
elementi.

Si trova nel package java.util e va importata nella classe che la vuole usare.

Implementa l’interfaccia List e quindi ne definisce tutti i metodi.

La documentazione per questa classe può essere trovata qui:

https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/ArrayList.ht
ml
ARRAYLIST - PROPRIETÀ FONDAMENTALI
★ Può contenere elementi duplicati.
★ Mantiene l’ordine di inserimento.
★ Non è sincronizzata.
★ Permette accesso casuale ai suoi elementi basato su indice.
★ Manipolazione più lenta di LinkedList.
ARRAYLIST - COSTRUTTORI
❖ ArrayList()
➢ Crea una struttura vuota.
❖ ArrayList(Collection<? extends E> c)
➢ Crea una struttura partendo dalla collezione c.
❖ ArrayList(int capacity)
➢ Crea una struttura con capacità iniziale settata a capacity.

ArrayList è un tipo generico! Come si dichiara quindi?

ES: ArrayList<String> lista = new ArrayList<String>();


ARRAYLIST - POPOLARE
Una ArrayList si popola utilizzando il metodo add. Esso dispone dei seguenti
overload:
❖ void add(int index, E element)
➢ Inserisce l’elemento di tipo E (adeguato alla lista!) in posizione index.
❖ boolean add(E e)
➢ Inserisce l’elemento di tipo E alla fine della lista. Restituisce true se l’elemento è stato
aggiunto.
❖ boolean addAll(int index, Collection <? extends E> c)
➢ Inserisce la collezione c a partire dall’elemento index (omissibile - esiste overload senza
index!)

ES: lista.add(“Ciao!”);
ARRAYLIST - ACCESSO E MANIPOLAZIONE
È possibile accedere agli elementi della lista usando:

❖ E get(int index)
➢ Restituisce il valore dell’elemento di tipo E in posizione index.
■ ES: lista.get(0);

È possibile modificare il valore di un elemento della lista usando:

❖ E set(int index, E element)


➢ Imposta il valore dell’elemento in posizione index a element. Restituisce il valore precedente.
■ ES: lista.set(0, “Salve!”);
ARRAYLIST - ELIMINAZIONE
È possibile eliminare elementi dalla lista utilizzando:
❖ E remove(int index)
➢ Rimuove l’elemento in posizione index. Shifta ciò che vi era a destra verso sinistra. Restituisce
l’elemento eliminato.
■ ES: lista.remove(0);
❖ boolean remove(Object o)
➢ Rimuove la prima occorrenza di o. Restituisce true se l’elemento era in lista (quindi l’eliminazione è
avvenuta).
■ ES: lista.remove(“Salve!”);

Possibile anche resettare la lista tramite:


❖ void clear()
➢ Rimuove tutti gli elementi dalla lista.
■ ES: lista.clear();
ESERCIZI
❖ Creare una classe chiamata ArrayLists e creare al suo interno una ArrayList di
interi. Popolare la lista così che rappresenti un conteggio da 1 a 10.
❖ Stampare a schermo tutti gli elementi della ArrayList.
❖ Cancellare il secondo elemento della lista, quindi cancellare il penultimo.
Stampare a schermo i valori così cancellati.
❖ Pulire la lista e riempirla di nuovo con una serie di 30 interi tra 0 e 100
generati casualmente.
MODI MIGLIORI PER ITERARE
Esistono alcuni comodi modi per iterare all’interno di collezioni.

➔ Ciclo for combinato con metodo size() che restituisce il numero di elementi
della lista.
◆ for(int i = 0; i < lista.size(); i++) System.out.println(lista.get(i));
➔ Ciclo for-each
◆ for(E e : lista) System.out.println(“E”);

Oppure usando un Iterator...


ITERATOR
Iterator è un’interfaccia che fornisce metodi utili per iterare all’interno di classi che
implementano l’interfaccia Iterable, ovvero per i quali sono definiti metodi a
supporto dello scorrimento di diversi oggetti.
Tutte le classi e le interfacce del Collections Framework implementano Iterable!!!
Come si usa un iteratore?
❖ Iterator iterator()
➢ Restituisce un iteratore per la lista.
■ ES: Iterator iter = lista.iterator();

L’iteratore ora contiene tutti gli elementi della lista e punta al primo elemento.
METODI DI ITERATOR
❖ boolean hasNext()
➢ Ritorna true se l’iteratore ha elementi ancora da iterare.
■ ES: iter.hasNext();
❖ Object next()
➢ Fa tornare l’elemento puntato dal cursore e muove il cursore al prossimo.
■ ES: String s = iter.next();
❖ void remove()
➢ Rimuove l’ultimo elemento fatto tornare dall’iteratore.
■ ES: iter.remove(); //Rimuove l’elemento dalla collezione!
ARRAYLIST - ITERARE CON ITERATOR
ARRAYLIST - ORDINAMENTO
Collections mette a disposizione un metodo in grado di ordinare liste di oggetti
che implementino l’interfaccia Comparable:

❖ Collections.sort();
➢ Metodo statico di Collections (java.util.Collections) che ordina gli elementi in base al loro
criterio.
■ Tipi base: ordine numerico (di codifica utf-8 per i caratteri).
■ Stringhe: ordine lessicografico.
■ Tutto il resto: compareTo().

ES: Collections.sort(lista);
ARRAYLIST - ALTRI METODI
❖ boolean isEmpty()
➢ Ritorna true se la lista riferita non contiene elementi.
❖ boolean contains(Object o)
➢ Ritorna true se la lista riferita contiene l’elemento o.
❖ int size()
➢ Restituisce il numero di elementi attualmente nella lista.
❖ int indexOf(Object o)
➢ Restituisce l’indice della prima occorrenza di o. -1 se non presente.
❖ int lastIndexOf(Object o)
➢ Restituisce l’indice dell’ultima occorrenza di o. -1 se non presente.
❖ Object[] toArray()
➢ Restituisce un array equivalente all’ArrayList.
ESERCIZI
● Partendo dall’ultima ArrayList generata, iterarla con un for-each e sostituire
ogni numero dispari con la decina successiva.
● Ordinare la lista ottenuta.
● Stampare a schermo la lista, iterandola mediante un Iterator.
● Cancellare tutti gli elementi non primi e non uguali a 50 della lista. Se al
termine la lista è vuota, comunicarlo a schermo.
● Restituire l’indice della prima e dell’ultima occorrenza di 50, stampando “Non
presente” in caso la lista non contenga 50.
LINKEDLIST
Una lista concatenata è una lista di elementi disposti in preciso ordine. L’idea di
una lista di questo tipo è che ogni suo nodo contenga un elemento (o una lista di
elementi) e un puntatore al nodo successivo.

Così come ArrayList, va importata dal pacchetto java.util e implementa anch’essa


l’interfaccia List.

La documentazione riguardante LinkedList può essere consultata al seguente link:

https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/LinkedList.ht
ml
LINKEDLIST - PROPRIETÀ FONDAMENTALI
★ Può contenere elementi duplicati.
★ Mantiene l’ordine di inserimento.
★ Non è sincronizzata.
★ Manipolazione più rapida di ArrayList.
★ Può essere usata come lista, come pila o come coda.
LINKEDLIST - COSTRUTTORI
❖ LinkedList()
➢ Crea una lista concatenata vuota.
❖ LinkedList(Collection <? extends E> c)
➢ Crea una lista concatenata a partire dalla collezione c.

Così come ArrayList, anche LinkedList usa i tipi generici e per questo deve essere
dichiarata in un modo simile al seguente:

ES: LinkedList<String> concat = new LinkedList<String>();


USO DI LINKEDLIST
In realtà, l’uso standard di LinkedList è riconducibile a quanto visto per ArrayList!

È possibile consultare le slide viste per ArrayList per avere un’idea generale sui
metodi utilizzabili con questa classe: merito dell’implementazione comune
dell’interfaccia List!

In realtà LinkedList offre più metodi, alcuni dei quali hanno però funzionalità molto
simili tra loro: questo perché LinkedList viene usata per rappresentare un buon
numero di strutture dati!
LISTA CONCATENATA
Lista composta da nodi in cui ogni nodo contiene uno o più elementi e un
puntatore al nodo successivo.

L’accesso alla lista non è casuale! Seppure possiamo lavorare su punti precisi
della lista, il sistema scorre tutti gli elementi dal primo a quello trovato! La
manipolazione è veloce ma l’accesso è lento!
LISTA DOPPIAMENTE CONCATENATA
È come una lista concatenata ma ogni nodo ha anche un puntatore al nodo
precedente. Si può scorrere in entrambe le direzioni, velocizzando la ricerca in
una lista ordinata se cerchiamo un elemento che sappiamo essere più vicino al
massimo!
ITERARE UNA LISTA DOPPIAMENTE CONCATENATA
Per iterare nella direzione standard è possibile usare un Iterator generato dal
metodo iterator() già visto per ArrayList.

Per scorrere nell’altra direzione possiamo usare:

❖ Iterator<E> descendingIterator()
➢ Restituisce un Iterator che scorre la lista in ordine inverso.
■ ES: Iterator revit = concat.descendingIterator();
ESERCIZI
● Creare una classe ListaDoppiamenteConcatenata che crea una lista
doppiamente concatenata di 10 interi compresi tra 0 e 100 generati in modo
casuale.
● Stampare il contenuto della lista sia da sinistra a destra che da destra a
sinistra.
● Cercare i metodi adatti per rimuovere il primo e l’ultimo elemento della lista.
● Aggiungere altri due elementi casuali tra 0 e 100 alla coda destra della lista.
PILA
Struttura dati conosciuta anche come stack che obbedisce alla politica LIFO (Last
In First Out). Le operazioni su questa struttura sono limitate e hanno nomi precisi:

➔ push: Inserisce un elemento in cima (al termine) della pila.


➔ pop: Rimuove l’elemento in cima (al termine) della pila, restituendolo.
➔ top: Restituisce l’elemento in cima (al termine) della pila.
➔ isEmpty: Restituisce true se la pila è vuota.
PILA CON LINKEDLIST
❖ void push(E e)
➢ Effettua l’operazione di push dell’elemento e sulla pila rappresentata da LinkedList<E>
❖ E pop()
➢ Effettua l’operazione di pop dalla pila rappresentata da LinkedList<E>
❖ E peek()
➢ Effettua l’operazione di top per la pila rappresentata da LinkedList<E>. Simula isEmpty(): true
se non null; false se null.
CODA
Struttura dati che obbedisce alla politica FIFO (First In First Out). Le operazioni su
questa struttura sono limitate e hanno nomi precisi:

➔ enqueue: Accoda un elemento aggiungendolo al termine della coda.


➔ dequeue: Rimuove il primo elemento dalla coda e lo restituisce. La coda
shifta a sinistra.
➔ first: Restituisce il primo elemento della coda.
➔ isEmpty: Restituisce true se la coda è vuota.
CODA CON LINKEDLIST
❖ boolean offerLast(E e)
➢ Effettua enqueue per la coda rappresentata da LinkedList<E>, restituendo true se l’operazione
è avvenuta.
❖ E poll()
➢ Effettua dequeue per la coda rappresentata da LinkedList<E>.
❖ E peek()
➢ Effettua first per la coda rappresentata da LinkedList<E>. Simula anche isEmpty poiché
restituisce null se la coda è vuota.
ESERCIZI
● Scrivere una classe PilaCoda in cui creare una pila di interi usando LinkedList
ed effettuare 4 volte l’operazione di push per 4 interi generati casualmente tra
0 e 100. Svuotare la pila stampando gli elementi rimossi in ordine di
rimozione.
● Effettuare operazioni analoghe alle precedenti per una coda che usa
LinkedList per essere rappresentata.
ARRAYLIST VS LINKEDLIST

ARRAYLIST LINKEDLIST

Usa un array dinamico per contenere i dati. Usa una lista doppiamente concatenata per
contenere i dati.

Manipolazione lenta per via degli shift. Manipolazione veloce per via dell’assenza di shift.

Agisce solamente come lista. Agisce come lista, come coda e come pila (e altre!)

Ottima per immagazzinare e accedere ai dati. Ottima per manipolare i dati.


L’INTERFACCIA LIST
Sia ArrayList che LinkedList implementano l’interfaccia List, motivo per cui
condividono la stragrande maggioranza dei metodi.

È possibile fornire un’implementazione a List a partire da tutto ciò che la


implementa nella seguente maniera:

List<tipo> nomeLista = new ClasseLista<tipo>(parametri);

ES: List<String> list1 = new ArrayList<String>();

ES: List<Integer> list2 = new LinkedList<Integer>();


CONVERSIONI ARRAY/LISTA
Da array a lista:

1. Creare l’array arr che si vuole convertire in lista


2. List<tipoArray> list = new ArrayList<tipoArray>();
3. for (tipoArray ta : arr) list.add(ta);

Da lista a array:

1. Creare la lista list che si vuole convertire in array.


2. tipoArray[] arr = list.toArray(new tipoArray[list.size()]);

Nota: Per stampare comodamente un array esiste il metodo Arrays.toString(arr)


ESEMPIO LISTA -> ARRAY
LISTITERATOR
ListIterator è un iteratore molto potente da utilizzare con le List. Esso viene
generato attraverso:
ListIterator<tipo> li = lista.listIterator();
Dispone di molti metodi:
❖ void add(E e)
➢ Inserisce l’elemento e nella lista correlata.
❖ boolean hasNext()
➢ Restituisce true se la lista ha almeno un elemento ancora da iterare.
❖ E next()
➢ Restituisce il prossimo elemento della lista e avanza di 1 il cursore.
LISTITERATOR - ALTRI METODI
❖ int nextIndex()
➢ Restituisce l’indice dell’elemento che verrebbe restituito dal prossimo next().
❖ boolean hasPrevious()
➢ Ritorna true se l’iteratore ha almeno un elemento alla sinistra del cursore.
❖ E previous()
➢ Restituisce l’elemento appena a sinistra del cursore e sposta quest’ultimo di una posizione
indietro.
❖ E previousIndex()
➢ Come nextIndex(), ma nella direzione opposta.
❖ void remove()
➢ Rimuove dalla lista l’elemento fatto ritornare dalla precedente next() o previous().
❖ void set(E e)
➢ Rimpiazza l’ultimo elemento fatto ritornare dalla precedente next() o previous() con e.
ESERCIZI
● A partire dalla classe PilaCoda, usare un ListIterator per aggiungere altri 3
numeri generati casualmente tra 0 e 100 alla lista.
● Attraversare la lista fino a trovare un elemento maggiore di 50, sostituendolo
con il suo simmetrico (ES: 70 diventa 30) utilizzando il ListIterator. Se non ci
sono elementi maggiori di 50, non fare nulla.
CODE CON QUEUE
Abbiamo visto come utilizzare LinkedList in modo da implementare una coda. Il
Collections Framework in realtà mette a disposizione un’interfaccia apposita per
gestire oggetti di questo tipo: Queue.

Essa offre i seguenti metodi:

❖ boolean offer(E e)
➢ Aggiunge l’elemento e alla coda.
❖ E poll()
➢ Restituisce e rimuove l’elemento in testa alla coda, null se la coda è vuota.
❖ E peek()
➢ Restituisce senza rimuovere l’elemento in testa alla coda, null se la coda è vuota.
PRIORITY QUEUE
PriorityQueue è una classe che implementa Queue: il suo scopo è quello di
realizzare una coda in cui gli elementi non sono ordinati secondo una politica
FIFO ma secondo una politica basata su priorità: ad ogni elemento è associato un
valore comparabile di priorità e gli elementi con priorità più alta escono per primi
dalla coda.

Le operazioni di poll() eliminano prima gli elementi reputati minori dal metodo
compareTo() che agisce come decisore del grado di priorità.
PRIORITYQUEUE - ESEMPIO
HASHSET
Un set è una lista che ha una regola aggiuntiva: non può contenere chiavi
duplicate.

Tale struttura dati è rappresentata da un’interfaccia Set (che nulla ha che vedere
con List, ma è molto simile).

HashSet è una classe che implementa tale interfaccia e che garantisce un rapido
accesso ai suoi elementi grazie ad una tecnica chiamata “hashing” in cui ad ogni
valore è associata una chiave univoca usata per accedervi e che viene usata per
ordinare il set.
HASHSET - PROPRIETÀ FONDAMENTALI
★ Usa l’hashing per memorizzare i dati
★ Contiene solo valori unici
★ Permette valori nulli
★ Non è sincronizzabile
★ Non mantiene l’ordine di inserimento, lasciando tale responsabilità alla chiave
★ Miglior approccio per le operazioni di ricerca
★ Capacità iniziale: 16
★ Fattore di carico (quando incremento la capacità?): 0,75 (75% dell’attuale)
HASHSET - COSTRUTTORI
❖ HashSet()
➢ Crea un HashSet vuoto di default (capacità 16 e loading factor 0,75).
❖ HashSet(int capacity)
➢ Crea un HashSet vuoto con capacità iniziale pari a capacity e loading factor 0,75.
❖ HashSet(int capacity, float loadfact)
➢ Crea un HashSet vuoto con capacità iniziale pari a capacity e loading factor loadfact.
❖ HashSet(Collection <? extends E> c)
➢ Crea un HashSet a partire dalla collezione c.

Anche in questo caso si usano i tipi generici!

ES: HashSet<String> hs = new HashSet<String>();


HASHSET - METODI
Seppur List e Set non hanno nulla in comune se non l’essere entrambe
sottointerfacce di Collection, esse condividono la maggior parte dei metodi che
funzionano in modo simile.

add(), remove(), iterator() e molti altri sono presenti come metodi di HashSet e
permettono di manipolarlo in modo simile ad una lista. Si noti che aggiungere un
elemento già presente fa semplicemente fallire l’add, ma non causa errori!
ESERCIZI
● Una biblioteca ha necessità di catalogare i propri libri ma ne ha davvero tanti.
Per questo motivo l’amministrazione ha deciso di catalogare prima i libri con
più pagine. Creare una classe Libro che abbia campi per rappresentare un ID,
il titolo, il numero di pagine e l’autore e creare una classe LibroDemo che
aggiunga una serie di libri ad una coda con priorità. Effettuare il dequeue di
tutti i libri stampandone a schermo i dettagli ogni volta che un libro lascia la
coda.
● A partire dalla classe ArrayLists, creare un HashSet a partire dalla collezione
realizzata e ricercare l’elemento massimo di tale collezione.
MAPPE
Una mappa è una struttura dati che fa corrispondere ad un elemento di un certo
tipo detto chiave un altro elemento di un altro tipo detto valore.

Per supportare questa struttura dati esistono due interfacce: Map e SortedMap (la
seconda è sottointerfaccia della prima).

Vedremo l’utilizzo di una mappa che sfrutta l’hashing: HashMap!


HASHMAP
In una HashMap è possibile memorizzare coppie di chiavi e valori in cui le prime sono
uniche: inserire un elemento con la stessa chiave sostituisce quello presente.

Come tutte queste classi, va importata.

HashMap utilizza due parametri: <K, V> dove:

➔ K è il tipo della chiave


➔ V è il tipo del valore associato alla chiave

La documentazione riguardante la HasMap è consultabile al seguente link:

https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/HashMap.html
HASHMAP - COSTRUTTORI
❖ HashMap()
➢ Genera una HashMap con valori di capacità e fattore di carico di default (16 e 0,75).
❖ HashMap(Map <? extends K, ? extends V> m)
➢ Genera una HashMap a partire da un altro elemento che implementa Map.
❖ HashMap(int capacity)
➢ Genera una HashMap con capacità capacity e fattore di carico di default.
❖ HashMap(int capacity, float loadfact)
➢ Genera una HashMap con capacità capacity e fattore di carico loadfact.

Le HashMap usano i tipi generici e si dichiarano usando due parametri.

ES: HashMap<Integer, String> hm = new HashMap<Integer, String>();


HASHMAP - POPOLARE
❖ V put (K key, V value)
➢ Inserisce un valore value con chiave key nella HashMap, restituendo il vecchio valore se key è
già presente, altrimenti restituisce null.
❖ void putAll(Map <? extends K, ? extends V> m)
➢ Inserisce tutti gli elementi di m nella HashMap.
❖ V putIfAbsent(K key, V value)
➢ Inserisce un valore value con chiave key nella HashMap e fa tornare null solo se key non è già
presente; altrimenti fa tornare il valore associato a tale chiave.
HASHMAP - ITERAZIONE
Per iterare attraverso una HashMap in modo comodo è possibile utilizzare
l’interfaccia Map.Entry che offre metodi per recuperare chiave e valore per ogni
entry della mappa.
In questo modo è possibile utilizzare i metodi:
❖ K getKey()
➢ Restituisce la chiave associata all’entry riferita.
❖ V getValue()
➢ Restituisce il valore associato all’entry riferita.

Per fare ciò è necessario prima convertire la mappa in un “set di entry”.


ES:
HASHMAP - RIMOZIONE
La rimozione in una HashMap può avvenire secondo chiave o secondo una
combinazione di chiave e valore.

❖ V remove(Object key)
➢ Rimuove la entry con la chiave specificata, facendo ritornare il valore così eliminato.
❖ boolean remove(Object key, Object value)
➢ Rimuove la entry con la chiave specificata solo se il valore è quello atteso, facendo ritornare
true o false a seconda che l’eliminazione sia avvenuta o meno.
❖ void clear()
➢ Svuota la HashMap.
HASHMAP - ACCESSO E RIMPIAZZO
Ovviamente, è possibile accedere in modo diretto ai valori in base alla chiave.

❖ V get(Object key)
➢ Restituisce il valore associato alla chiave key; null se tale chiave non è presente.
❖ V replace(K key, V value)
➢ Rimpiazza il valore associato a key con value, restituendo il vecchio valore.
❖ boolean replace(K key, V oldValue, V newValue)
➢ Rimpiazza il valore associato a key con newValue solo se il valore precedente è quello
specificato in oldValue. Restituisce true se la sostituzione avviene, altrimenti restituisce false.
HASHMAP - ALTRI METODI
❖ boolean isEmpty()
➢ Ritorna true se la HashMap non contiene entry.
❖ boolean containsValue(Object value)
➢ Ritorna true se la HashMap contiene almeno una entry che abbia valore value.
❖ boolean containsKey(Object key)
➢ Ritorna true se la HashMap contiene almeno una entry che abbia chiave key.
❖ int size()
➢ Restituisce il numero di entry della HashMap.
ESERCIZI
● Creare una HashMap che contenga 10 valori interi generati casualmente tra 0
e 100 associati a delle chiavi a loro volta intere. Stampare la HashMap a
schermo.
● A partire dalla prima HashMap, crearne una seconda contenente tutti gli
elementi della prima e aggiungervi 8 valori casuali aggiuntivi. Stampare a
schermo, per ogni entry, il valore se questo è dispari, la somma tra il valore e
la chiave se il valore è pari (e sostituirlo al valore attuale) e rimuovere l’entry
senza stamparla se la chiave è un numero primo.
CENNI AL HASHCODE
In passato abbiamo visto come il metodo ereditato da Object toString() se non
ridefinito restituisca una stringa composta dal seguente formato:

“NomeClasse@hashcode”

L’hashcode è l’indirizzo dell’oggetto in memoria e prende questo nome per una


ragione ben specifica: è utilizzato per stabilire il valore della chiave in una
Collection che utilizza l’hashing.

Un qualsiasi tipo di oggetto può funzionare da chiave poiché il suo indirizzo in


memoria è unico!
HASH E LISTE
Tuttavia abbiamo visto che HashSet e HashTable hanno una certa capacità.
L’hashcode non è utilizzato come chiave “as it is” ma viene trasformato usando la
seguente formula:
hashcode&(capacity-1)
In questo modo l’hashcode diventa un numero intero incluso in un range tra 0 e
capacity-1. Hashcode trasformati nello stesso numero vengono accodati all’entry
associata a tale indice della tabella Hash, creando una lista di entry in tale
posizione.
La teoria dietro le tabelle Hash è questione di algoritmica e di studio delle strutture
dati, non argomento di questo corso.
LA CLASSE COLLECTIONS
La classe Collections è una classe dotata unicamente di metodi statici che permettono
di lavorare in modo rapido ed efficace con qualsiasi classe offerta dal framework.

Ci sono due aspetti importanti da considerare:

★ I metodi statici offrono algoritmi che si comportano in modo polimorfico per operare
sulle collezioni.
★ Passare collezioni o oggetti uguali a null ai metodi di questa classe causa
un’eccezione di NULLPOINTEREXCEPTION.

Per la documentazione:

https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/Collections.html
ESERCIZI
● Scrivere una classe CollezioniMetodi che crei una ArrayList di valori double
diversi da 0 richiesti in input all’utente. I valori vengono richiesti fino a che
l’utente non inserisce uno 0.
● Aggiungere 3 elementi alla lista: 12.0, 24.0 e 36.0 usando il metodo
opportuno della classe Collections. Riordinare quindi la lista in ordine
naturale.
● Trovare il massimo e il minimo della lista utilizzando i metodi della classe
Collection.
● Scambiare di posto il primo e l’ultimo elemento della lista.
I/O
TUTTO FLUISCE
Il traffico di I/O non avviene tutto in una volta, piuttosto avviene come una
sequenza di dati, detta stream (flusso).
Uno stream è composto da bytes e viene anche chiamato bytestream per tale
motivo. L’idea è che il flusso continui a trasportare dati dal momento in cui viene
aperto a quello in cui viene chiuso. Alle volte il flusso è chiamato anche “canale”.
Java ha 3 tipi di flusso sempre attivi che si occupano di gestire le comunicazioni
con la console:
★ System.in Canale standard di input
★ System.out Canale standard di output
★ System.err Canale standard di errore
ESEMPI DI USO
GESTIONE DELL’IO
Tutte le classi volte a gestire l’IO in Java sono incluse nel package java.io e
vanno quindi importate nella classe prima di poter essere utilizzate.

La gestione di divide in due macro-categorie di classi:

➔ Classi che gestiscono i flussi in output: la gerarchia di OutputStream


➔ Classi che gestiscono i flussi in input: la gerarchia di InputStream

I dati possono provenire (input) ed andare (output) da/a file di tipo diverso.

L’idea è che i dati vengano letti da una sottoclasse di InputStream e scritti da una
classe di OutputStream dopo essere stati sottoposti alle volute manipolazioni.
FILE SYSTEM
Ogni SO odierno implementa una struttura famosamente conosciuta con il nome di File
System. Il funzionamento di questo sistema è complesso e oggetto di molti corsi sui
Sistemi Operativi, ma a noi basta sapere la proprietà fondamentale di questo sistema:
Tutto ciò che è gestito dal Sistema Operativo è visto come un file, ovvero una collezione
di dati.
Questo implica che anche le periferiche di input e output vengono viste come files e per
questo il File Handling può essere gestito dagli stream a prescindere dal tipo di sorgente
e destinazione.
OUTPUTSTREAM
OutputStream è una classe astratta che agisce come capostipite della gerarchia delle classi di gestione
dell’output. Ogni sottoclasse di OutputStream è una sua specializzazione.

Lo scopo di questa gerarchia è accettare byte e spedirli al corretto output.

Metodi Fondamentali:

❖ abstract void write(int b) throws IOException


➢ Scrive il byte specificato nello stream riferito.
❖ void write(byte[] b) throws IOException
➢ Scrive un array di byte nello stream riferito.
❖ void write(byte[] b, int off, int len) throws IOException
➢ Scrive len byte dell’array b a partire dal byte numero off.
❖ void flush() throws IOException
➢ Svuota lo stream riferito.
❖ void close() throws IOException
➢ Chiude lo stream riferito.
INPUTSTREAM
InputStream è l’equivalente di OutputStream per quanto riguarda tutte le classi che riguardano la
gestione dei flussi in input.

Metodi Fondamentali:

❖ abstract int read() throws IOException


➢ Legge il prossimo byte dallo stream riferito, restituendo -1 se il file è giunto al termine.
❖ int read(byte[] b)
➢ Legge i byte presenti nello stream riferito inserendoli nell’array b. Restituisce il numero di byte letti.
❖ int read(byte[] b, int off, int len)
➢ Legge len byte dallo stream riferito a partire dal byte off e li inserisce nell’array b, restituendo il numero di byte letti.
❖ byte[] readAllBytes()
➢ Legge i byte presenti nello stream riferito e restituisce un array che li contiene tutti.
❖ int available()
➢ Restituisce il numero di byte ancora presenti nello stream.
❖ void close()
➢ Chiude lo stream riferito.
GERARCHIE DI STREAM

Si noti il parallelismo!
FILE IN JAVA
La classe File di Java consente di rappresentare astrattamente il nome di un path
(assoluto o relativo) di un file o di una directory. Tale path è conosciuto come
“nome del file”.

Come al solito, si lascia il link alla documentazione:

https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/io/File.html

Il costruttore principale della classe è:

❖ File(String pathname)
➢ Crea un oggetto contenente il pathname (assoluto o relativo) fornito come argomento.
METODI PRINCIPALI DI GESTIONE DEI FILE
❖ boolean createNewFile()
➢ Crea un nuovo file vuoto al pathname riferito e solo se tale file non esiste. Restituisce true se il file è stato creato.
❖ boolean mkdir()
➢ Crea una directory al pathname fornito.
❖ boolean canWrite()
➢ Controlla se il file riferito dal pathname è scrivibile dall’applicazione.
❖ boolean canRead()
➢ Controlla se il file riferito dal pathname è leggibile dall’applicazione.
❖ boolean canExecute()
➢ Controlla se il file riferito dal pathname è eseguibile dall’applicazione.
❖ boolean isDirectory() / isFile()
➢ Controlla se il pathname si riferisce ad una directory/file.
❖ String getName()
➢ Restituisce il nome del file o della directory riferita dal pathname.
❖ String[] list()
➢ Restituisce un array di stringhe rappresentanti i nomi dei file e delle directory al pathname della directory fornito.
❖ String[] listFiles()
➢ Restituisce un array di stringhe rappresentanti i nomi dei file al pathname della directory fornito.
ESERCIZI
● Scrivere una classe FileTest che crei un file vuoto nella directory corrente e
stampi su standard output se il file è stato creato o meno.
● Creare altri 3 file vuoti nella directory corrente, quindi stampare su standard
output il nome di tutti i file presenti nella directory.
● Creare una nuova directory nella directory corrente. Stampare i nomi di tutti i
file e di tutte le directory nella directory corrente su standard output.
● Stampare informazioni riguardo la possibilità di eseguire/leggere/scrivere i file
della directory corrente e anche il loro peso in byte (usare il metodo length()).
FILEOUTPUTSTREAM
FileOutputStream è una classe che gestisce un flusso direzionato ad un file per
scrivere in esso.
Utile per scrivere tipi primitivi tramite un flusso di byte. Per dati orientati ai
caratteri, è meglio usare la classe FileWriter.
Importabile tramite import java.io.FileOutputStream.
Link alla documentazione:
https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/io/FileOutputStr
eam.html
FILEOUTPUTSTREAM - COSTRUTTORI
❖ FileOutputStream(File f)
➢ Crea uno stream di output verso il file f.
❖ FileOutputStream(String s)
➢ Crea uno stream di output verso il file nominato come s.

Entrambi sollevano eccezioni, ad esempio se il file non è stato trovato. Per


gestirle, o si gestisce IOException o si gestiscono le singole eccezioni con
molteplici catch.
FILEOUTPUTSTREAM - METODI
Metodi degni di nota della classe sono:

❖ write e i suoi vari overload


❖ close
FILEOUTPUSTREAM - ESEMPIO
OSSERVAZIONI
➢ I flussi di byte trasportano, appunto, byte. Per trasferire qualsiasi tipo di dato,
dobbiamo prima convertirlo in un array di byte! La classe String offre il
metodo getBytes() per effettuare questa conversione.
➢ Per gli altri tipi di dato? Nell’incertezza, trasformate in stringa grazie al metodo
toString e poi effettuate getBytes()! toString è un metodo molto importante!
➢ Il pathname non deve necessariamente puntare ad un file già esistente: se il
percorso è corretto viene creato il file in quel percorso con il nome indicato.
Nell’esempio, se “esempio.txt” non esiste, viene creato. Si noti che i ‘\’ devono
essere preceduti da un secondo ‘\’ per fare escape!
➢ Le eccezioni di IO possono essere gestite in molti modi personalizzabili, ma
per ora ci limitiamo a usare il metodo printStackTrace()
FILEINPUTSTREAM
FileInputStream è una classe che permette di ricevere flussi di byte in ingresso a
partire da un file situato in un preciso pathname. Il file può essere di tipo
qualunque: audio, video, testo, etc…
Per gli stream di caratteri, si consiglia di usare la classe FileReader.
Importabile tramite import java.util.FileInputStream.
Link alla documentazione:
https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/io/FileInputStre
am.html
FILEINPUTSTREAM - COSTRUTTORI E METODI
I costruttori sono simili a quelli di FileOutputStream poiché ci serve la locazione
del file da leggere e la recuperiamo in modo analogo.

Metodi degni di nota sono:

❖ read e i suoi overload


❖ available
❖ close
❖ long skip(long x) throws IOException
➢ Salta x byte dallo stream di ingresso e li scarta. Restituisce il numero effettivo di byte scartati.
FILEINPUTSTREAM - ESEMPIO
ESERCIZI
● Usando le classi FileInputStream e FileOutputStream scrivere una classe
SommaIO che legga due valori interi da due file diversi, ne faccia la somma e
che la stampa a schermo.
● Scrivere in un nuovo file non esistente “La somma tra i due numeri è: ”
seguita dal numero. A capo, aggiungere “Grazie per aver usato SommaIO!”.
● Provare a fornire dei pathname non validi per comprendere il funzionamento
di printStackTrace().
FILEWRITER
FileWriter è la classe standard di Java progettata per la scrittura di output sotto
forma di uno o più caratteri.
La principale differenza con FileOutputStream è che non vi è bisogno di convertire
le stringhe e i caratteri in bytes siccome mette a disposizione metodi che si
occupano di scrivere direttamente tali tipi di dato.
Importabile tramite import java.io.FileWriter.
Link alla documentazione:
https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/io/FileWriter.ht
ml
FILEWRITER - COSTRUTTORI
❖ FileWriter(String s)
➢ Apre un flusso di output verso il file localizzato dal pathname rappresentato da s.
❖ FileWriter(File f)
➢ Apre un flusso di output verso il file localizzato dal pathname f.

Anche in questo caso vengono sollevate eccezioni di IO, quindi bisogna gestirle
così come visto per FileOutputStream e FileInputStream!
FILEWRITER - METODI
❖ void write(String text)
➢ Scrive text nel file.
❖ void write(char c)
➢ Scrive c nel file.
❖ void write(char[] cs)
➢ Scrive l’array di caratteri nel file.
❖ void flush()
➢ Svuota lo stream.
❖ void close()
➢ Chiude lo stream.
FILEREADER
FileReader è la classe java che si occupa di leggere sequenze di caratteri dai file.
La lettura avviene in modo simile a FileInputStream e quindi i caratteri arrivano al
programma sotto forma di bytes e quindi vanno riconvertiti in caratteri.
Può essere utile utilizzare la classe StringBuilder per costruire stringhe a partire
dai caratteri convertiti.
Importabile tramite import java.io.FileReader.
Link alla documentazione:
https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/io/FileReader.ht
ml
ESERCIZI
● Scrivere una classe StampaCaratteri che usi FileReader e FileWriter per
leggere 4 stringhe distribuite su 4 righe di un file.
● Stampare in un nuovo file “caratteri.txt” i caratteri delle 4 stringhe secondo il
seguente criterio: primo carattere della prima stringa, primo carattere della
seconda stringa, primo carattere della terza stringa, etc… fino a che tutti i
caratteri sono stati stampati. Ricordarsi di gestire il caso in cui le stringhe
hanno diverse dimensioni.
IO E OGGETTI: LA SERIALIZZAZIONE
Il concetto di serializzazione in Java indica la capacità di codificare lo stato di un
oggetto (i valori attuali dei suoi campi) in modo tale da poterlo scrivere in un flusso
di byte.
La conversione inversa da flusso di byte allo stato dell’oggetto attuale viene
chiamata deserializzazione.
La comodità di questa tecnica è che è indipendente dalla piattaforma: possiamo
serializzare un oggetto su un sistema e deserializzarlo su un altro! Portabilità
massima, soprattutto per la comunicazione in rete (marshalling).
Per poter essere serializzato, un oggetto deve implementare l’interfaccia
Serializable.
L’INTERFACCIA SERIALIZABLE
L’interfaccia Serializable ha un corpo davvero difficile da ricordare:

...già, tutto qui. Ma perché?

Serializable è una delle così dette marker interfaces: interfacce il cui unico scopo
è comunicare che gli oggetti di una determinata classe possano subire il
serializzazione. Per implementarla bisogna importare java.io.Serializable.

Per poter rendere serializzabili gli oggetti di una classe, si deve semplicemente
scrivere:
OBJECTOUTPUTSTREAM
ObjectOutputStream è la classe di Java che permette di scrivere in un flusso di
output degli oggetti che poi potranno essere scritti dalla classe ObjectInputStream.
La codifica dell’oggetto avviene in base al nome e ai valori dei suoi campi. Essa
può essere scritta in un file o trasferita in una rete. L’oggetto deve essere
serializzabile.
Bisogna importare java.io.ObjectOutputStream per utilizzare la classe.
Link alla documentazione:
https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/io/ObjectOutput
Stream.html
OBJECTOUTPUTSTREAM - COSTRUTTORI
❖ ObjectOutputStream(OutputStream out) throws IOException
➢ Crea uno stream di output per oggetti che scrive in uno specifico output stream

Possiamo notare che c’è bisogno di creare un oggetto sottotipo di OutputStream:


generalmente FileOutputStream.

Ad ES:

FileOutputStream out = new FileOutputStream(“destinazione”);

ObjectOutputStream objout = new ObjectOutputStream(out);


OBJECTOUTPUTSTREAM - METODI PRINCIPALI
❖ final void writeObject(Object obj)
➢ Scrive lo stato dell’oggetto nel flusso di output.
❖ void flush()
➢ Svuota il flusso di output.
❖ void close()
➢ Chiude il flusso di output
❖ void writeTipo(Tipo t)
➢ Scrive un dato del tipo consono nel flusso di output.
OBJECTINPUTSTREAM
ObjectInputStream è la classe gemella di ObjectOutputStream e può essere
utilizzata per leggere i dati precedentemente serializzati da tale classe. Si occupa
quindi di compiere la deserializzazione.

Bisogna importare java.io.ObjectInputStream per poter utilizzare la classe.

Link alla documentazione:


https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/io/ObjectInputSt
ream.html
OBJECTINPUTSTREAM - COSTRUTTORI
❖ ObjectInputStream(InputStream in) throws IOException
➢ Crea un flusso di input per oggetti che legge dal InputStream specificato.

Possiamo notare che c’è bisogno di creare un oggetto sottotipo di InputStream:


generalmente FileInputStream.

Ad ES:

FileInputStream in = new FileInputStream(“sorgente”);

ObjectInputStream objin = new ObjectInputStream(in);


OBJECTINPUTSTREAM - METODI PRINCIPALI
❖ final Object readObject(Object obj)
➢ Legge un oggetto dal flusso in input. Attenzione che solleva anche ClassNotFoundException()!
❖ void close()
➢ Chiude il flusso di input.
IO DI OGGETTI - ESEMPIO (1)
IO DI OGGETTI - ESEMPIO (2)
EREDITARIETÀ E AGGREGAZIONE
Se una superclasse implementa l’interfaccia Serializable, allora tutte le sue
sottoclassi ereditano tale proprietà e non dovrà essere specificata nuovamente.

Se una classe serializzabile fa riferimento a classi non serializzabili nei suoi


campi, ad esempio definendo un campo di un tipo creato da un’altra classe non
implementante Serializable, allora il programma incontrerà un’eccezione a runtime
quando cercherà di serializzare un oggetto prodotto da quella classe. In poche
parole: tutti gli oggetti all’interno di una classe che si vuole serializzare devono
essere serializzabili.
SERIALIZZAZIONE DI MEMBRI STATICI E COLLEZIONI
I membri statici di un oggetto non vengono serializzati poiché in realtà sono
membri della classe e non dell’oggetto.

Per quanto riguarda le collezioni o anche solo gli array, per essere serializzabili
devono essere serializzabili tutti gli elementi contenuti.
TRANSIENT
La keyword transient altro non è che un modificatore al pari di final, abstract,
etc…

Lo scopo di transient è quello di marcare quei membri della classe che non
vogliono essere coinvolti nel processo di serializzazione.

Se si deserializzano dei dati di un oggetto prodotto da una classe che ha dei


membri transient, verrà generato un oggetto che ha valori di default per tali
membri.
ESERCIZI
● Creare una classe Cameriere che descriva campi per un cameriere così da
registrarne:
○ Nome
○ Età
○ Data di nascita
○ Data di inizio contratto
● Scrivere una classe Registra che sia dotata di un metodo main che crea tre
oggetti di tipo cameriere e li scriva in un file “registro.txt”, evitando però di
riportare l’età.
● Stampare in standard output il contenuto dell’intero registro una volta
terminata la creazione.
PRINTSTREAM
PrintStream è una classe Java che permette di scrivere dati da un flusso all’altro.
Essa effettua il flush dei dati automaticamente e quindi non richiede mai di
chiamare tale metodo. Inoltre non genera eccezioni di IOException.
Essa traduce i dati che i suoi metodi prendono come argomento dal tipo originale
in un testo e lo invia al flusso di output secondo una determinata formattazione.
Richiede di importare il pacchetto java.io.PrintStream.
Link alla documentazione:
https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/io/PrintStream.h
tml
PRINTSTREAM - COSTRUTTORI
Un oggetto PrintStream può essere realizzato in due modi:

❖ PrintStream(OutputStream out, boolean auto)


➢ Crea un oggetto PrintStream che scrive su out. auto è opzionale e specifica se l’autoflush sia
o meno attivo.
❖ PrintStream(String s, boolean auto)
➢ Crea un oggetto PrintStream che scrive in un file al pathname rappresentato da s. auto è
opzionale e specifica se l’autoflush sia o meno attivo.
PRINTSTREAM - METODI
❖ void print(tipo param)
➢ Scrive il param del tipo indicato nella destinazione dell’oggetto.
❖ void println(tipo param)
➢ Scrive il param del tipo indicato nella destinazione dell’oggetto e termina la riga.
❖ void printf(Object format, Object… args)
➢ Scrive il format nella destinazione dell’oggetto, sostituendo a tutti i marcatori di formato
rappresentati da un carattere preceduto da % (ES: %f indica un numero in virgola mobile) gli
argomenti nell’ordine in cui vengono presentati.

...un attimo!
Questi metodi in realtà sono i metodi che abbiamo sempre utilizzato al termine di
System.out!
PRINTSTREAM E SYSTEM
La classe System come riportato ad inizio di questa sezione rappresenta già uno
stream standard sempre presente orientato agli standard input, output e canale di
errore.

Questa classe può essere utilizzata invocando i metodi di PrintStream senza


bisogno di creare oggetti di tale tipo e senza importare classe all’interno del file
.java che si sta scrivendo!

Questo è possibile poiché il campo out di System è di tipo PrintStream!


PRINTSTREAM - ESEMPIO
PRINTSTREAM - ALTRI METODI
❖ void close()
➢ Chiude l’oggetto PrintStream riferito.
❖ boolean checkError()
➢ Effettua il flush dello stream e ne controlla lo stato di errore.
UN NUOVO MODO DI LEGGERE DA TASTIERA!
Un buon modo per leggere da tastiera che permette di usare in modo efficace le classi viste è l’uso
combinato di InputStreamReader e BufferedReader!

Entrambi vanno importati dal package java.io.

La “formula magica” sarebbe:

InputStreamReader in = new InputStreamReader(System.in);

BufferedReader br = new BufferedReader(in);

Entrambe sollevano eccezioni IOException e quindi bisogna gestire tale eccezione.

BufferedReader legge solo testi e lo si fa con br.readLine(), ma con l’uso dei vari metodi di parseType…

ES: double d = Double.parseDouble(br.next());


ESERCIZI
● Creare una classe LeggoStampo che usi un main per chiedere all’utente un
double, un int e una String tramite l’uso di BufferedReader e che stampi tali
input in un file e su schermo tramite l’uso di PrintStream.
SOMMARIO
In questa lezione abbiamo studiato:

● I tipi generici e il loro uso all’interno di classi generiche e non.


● Il Collection Framework e alcune delle classi di maggior uso.
● La gestione dell’IO con Java e alcune delle classi di maggior uso.

Potrebbero piacerti anche