You are on page 1of 15

In questa lezione le seguenti slide non stati trattati alla lavagna: queue, collections, convenzione lettere tipi

parametrici, notazione generics.

Lezione scorsa

Collections
Interfaccia Set
Implementazioni HashSet, TreeSet
Extends e super
Comparable e Comparator
Erasure e Cast

Scopo del corso : come si spezza in classi una applicazione e come fare le firme dei metodi, pi che la
loro implementazione (l'implementazione poi guidata dalle firme dei metodi; in alcune casi anche
importante l'implementazione dei metodi, come nel caso degli Iteratori).
La lezione scorsa abbiamo visto ancora cose sulle collection, in particolare sugli insiemi che hanno la
loro interfaccia dedicata che si chiama Set (in generale ogni collection ha una interfaccia specifica che
dice quali operazioni si possono fare a cui segue pi tipologie di implementazioni). Abbiamo visto che
tale interfaccia ha pi implementazioni diverse che differiscono per la complessit con cui vengono
implementate. Ad esempio in HashSet abbiamo che ogni operazione ha un tempo costante di
esecuzione, mentre nel TreeSet ogni operazione ha un tempo "logaritmico" di esecuzione. Comunque
quello che ci interessa sapere che Java mette a disposizione per ogni interfaccia delle
implementazioni diverse.
Abbiamo visto che il comparator necessario al TreeSet per poter funzionare.
Abbiamo visto anche che il compilatore controlla i tipi e che dopo a runtime i tipi generici spariscono,
per il compilatore ci assicura che a runtime non ci sar nessun problema (cio nessun eccezione
ClassCast viene sollevata). Quindi dobbiamo ricordarci che per i tipi parametrici (Generics) il controllo
viene fatto dal compilatore (e questa particolarit del linguaggio Java, in C++ il discorso diverso).

Queue:

Linterfaccia Queue una coda ovvero una struttura dove si inseriscono elementi
(ingresso in coda) e solo un elemento (la testa della coda) disponibile per laccesso o per
lestrazione (uscita dalla coda). La politica di selezione dellelemento in testa tipicamente
FIFO ma sono possibili anche code con scavalcamento basato su qualche criterio di
ordinamento o priorit.
Esistono tre operazioni base su una Queue:
inserimento di un elemento in coda
accesso allelemento in testa (senza toglierlo dalla coda)
estrazione dellelemento in testa (eliminandolo dalla coda)
Di ognuna esistono due versioni a seconda del comportamento quando loperazione non
possibile: una lancia uneccezione, laltra ritorna false o null
Lanciano eccezione:
- boolean add(E e): Aggiunta elemento
- E element(): Accesso elemento in testa
- E remove(): Estrazione elemento in
testa

Non lanciano eccezione:


- boolean offer(E e): Aggiunta elemento
- E peek(): Accesso elemento in testa
- E poll(): Estrazione elemento in testa

Eredita metodi dalla interfaccia Collection. Non pu essere usata per generare degli oggetti,
ma impone, alle classi che la utilizzano di implementare i seguenti metodi:
- public boolean add(T e); aggiunge un elemento nella coda indicato dal parametro T; se

l'elemento inserito viola le restrizioni di capacit legate alla coda, allora verr lanciata
l'eccezione IllegalStateException

- public boolean offer(T e); inserisce in una coda lelemento indicato dal parametro e solo

se linserimento attuabile. Tale metodo differisce dallequivalente metodo add in quanto, se


lelemento non stato inserito (per esempio perch limplementazione della coda ha posto
dei vincoli sulla capacit massima di elementi inseribili), non lancer alcuna eccezione
ritornando semplicemente il valore false .
- public T remove(); ritorna lelemento posto in testa a una coda, rimuovendolo; se la coda
vuota il metodo lancer uneccezione di tipo NoSuchElementException
- public T poll();ritorna lelemento posto in testa a una coda, rimuovendolo; se la coda
vuota, ritorner il valore null
- public T element();ritorna lelemento posto in testa a una coda senza rimuoverlo. Se la
coda vuota il metodo lancer uneccezione di tipo NoSuchElementException
- public T peek(); ritorna lelemento posto in testa a una coda senza rimuoverlo. se la coda
vuota, ritorner il valore null

L'implementazione della interfaccia Queue avviene tramite:


LinkedList: gi vista
PriorityQueue: log(n) per operazioni generiche mentre O(1) for peek, element.

Map:

E' l'ultima interfaccia che vediamo. Linterfaccia Map rappresenta un insieme di elementi
(o valori) ciascuno dei quali associato a una chiave che lo riferisce. Ricordiamo che una mappa non
pu avere chiavi duplicate e che ogni chiave pu riferire solamente un valore. Ogni coppia
chiave/valore definita anche entry.
Una mappa una interfaccia che contrariamente a tutte le collezioni viste finora ha due tipi generici.
La Lista la potevano fare di un solo tipo, la mappa invece mi dice "dato un qualcosa, tornami l'oggetto
vi associato". Quindi la mappa la possiamo vedere come una funzione dove dato un "qualcosa" K, ci
associo un "oggetto" V.
Vediamo innanzitutto come si usa e poi andiamo a vedere in dettaglio le caratteristiche. Immaginiamo
di dover far una struttura dati che memorizza il nome dello studente a cui corrisponde una certa
matricola. Tale struttura dati fatta cos: Map <Integer, String>, dove Integer sar il numero di
matricola e String il nome dello studente. Per la Map possiamo avere varie implementazioni e
scegliamo l'HashMap (la preferita del prof.), e all'inizio Studente una mappa vuota.
I dati dentro ad una mappa (che in questo caso una tabella Hash) si inseriscono con il metodo Put;
tramite il Put possiamo vedere Map come quella funzione che associa i valori K ai valori V: K il
dominio della funzione (ed anche chiamato chiave) e il V il codominio (ed anche chiamato value).
Osservazione. Map pu essere visto come una funzione:

f: K->V , K Dominio, V codominio, ad ogni elemento del dominio associato uno ed uno solo
elemento del codominio.
Se K finito,(|K|=n), allora possiamo rappresentare la funziona con una mappa.
1) Map<K,V> map = new ...

2) Per ogni k in K, map.put(k,f(k));


3) f(k) == map.get(k) per ogni k.
Quindi, data una Map<K,V> map; Possiamo definire la funzione f: K->V associata alla mappa map
nel seguente modo:
f(k) = v in V tale che v=map.get(k);
La funzione f rimane tale finch la mappa non viene modificata (es. tramite put o remove);
Per tutte le chiavi k in cui non definita la mappa, la funzione vale null.
Le mappe sono qualcosa di pi delle funzioni, perch le posso modificare anche a runtime
perch inserisco dati e le cose che non ho definito ritornano null.
Torniamo alla struttura che vogliamo definire:
Map<Integer, String> Studente = new HashMap<>(); //creo tabella Hash vuota
Studente.Put(2222, Paolo Rossi); //il numero il valore K e la stringa il valore V
Studente.Put(5555, Pinco Pallo);
Studente.Put(7777 , Pluto Nash);

Di norma come regola, nei linguaggi ad oggetti, come tecnica di programmazione, meglio usare
l'interfaccia al posto del tipo vero, questo perch se un domani mi accorgo che il codice scritto
funziona meglio su un TreeMap ( una cosa pi complessa da usare) piuttosto che su un HashMap,
pi semplice fare i cambiamenti. Addirittura se avessi un sottotipo, cio una classe che estende,
sempre meglio usare la classe base perch chiedo meno vincoli in caso di cambiamenti futuri.
Se voglio il nome di un certo studente uso il metodo get, che accede in tempo costante alla struttura
dati.
String nome = Studente.get(2222); //la chiave torna il valore associato
String s = Studente.get(7778); //get torna null se non trova il riferimento cercato, quindi a
//quella chiave associato null; quindi s prende null

Posso sovrascrivere le varie chiavi:


Studente.put(55, Carol);
Studente.put(55, Ilaria); //sovrascrive la riga precedente e quindi il valore
//associato alla medesima chiave

I metodi principali sono put (imposto il valore) e get. Ci sono poi tanti altri metodi, di cui tre particolari
e complessi. Iniziano da quelli pi semplici.
Altri metodi:
- int Size(); torna il numero di chiavi che sono state definite
- boolean isEmpty();

Nelle mappe abbiamo due tipi di contains, nelle altre collezioni solo uno.
- boolean contains Key (Object K); controlla se ce la chiave nella mappa
- boolean contains Value (Object K); controlla se ce il valore nella mappa
- V remove (Object K); data la chiave rimuove l'oggetto e torna il value

associato, altrimenti

torna null

V> m); aggiunge alla mappa creata, un'altra mappa


passata come argomento e possiamo passargli qualsiasi mappa che abbia dei tipi migliori di
quelli della mappa originaria, cio dei sottotipi per la chiave e value originarie
- void clear(); pulisce la mappa, come se la mappa fosse vergine, cio la resetta; questo
metodo pu essere utile per ottimizzare la memoria, soprattutto perch le mappe fatte con le
tabelle Hash occupano molta memoria
- putAll (Map<? extends K, ? extends

Mancano tre metodi che sono pi complessi; i primi due permettono di esaminare il contenuto
della mappa. La mappa non parte di quel gruppo che estendono le collection e collection
implementa Iterable, quindi le mappe non hanno la possibilit di essere iterate. Quindi la
mappa non si pu scorrere, ma posso usare i seguenti due metodi per scorrere o sulle chiavi o
sui valori:

- set<K> keySet(); scorre


- Collection<V> values();

sulle chiavi della mappa


scorre sui valori della mappa

Esempio per scorrere la mappa sui valori K e V:


1) for (String V:

Studente.values())

2) for (Integer K : Studente.keySet())

il ciclo 1) scorre <K=


sulla colonna
Integer

Integer

MAP
String

123

Pippo

456
876
34143

Pluto
Paperino
Pippo

=V -> qua il ciclo for del 2)


scorre
sulla colonna String

Notiamo che il keySet() un insieme, quindi non posso avere duplicati (perch se provo ad usare dei
duplicati, in realt sovrascrivo la medesima chiave con un nuovo valore, e di conseguenza o una unica
copia). Quindi le chiavi sono un insieme, i valori invece sono un collezione (e qua posso avere due
valori uguali, su chiavi diverse).
Il terzo metodo complicato il seguente:
- Set<Map.Entry<K,V>> entrySet(); questo metodo permettere di vedere le chiavi e i valori insieme,
come coppie; il metodo ritorna un insieme i cui elementi sono di tipo entry, che una classe definita
all'interfaccia Map (quindi una classe interna o sottointerfaccia); entry una coppia (chiave, valore)
e la indico con e. Essendo un insieme possiamo iterarlo (perch essendo un insieme una collection).
Vediamo come definita questa sottointerfaccia (che ha due tipi come la mappa) dentro la interfaccia
Map:
Interface Map<K, V>{ // definizione della
Static interface Entry<K,V>{
K.getKey();
V.getValue();
V.setValue(value); // consente di

sottointerfaccia entry; ha tre metodi

modificare, con un nuovo valore, il valore dellattuale

entry
}
}

A che serve? se facciamo un numero elevato di operazioni di modifica della mappa, invece di fare
tanti put e tanti get che potrebbero essere onerosi, usare una struttura come questa pu essere pi
veloce. Ad esempio supponiamo di avere una mappa che sia da intero ad intero e voglia trasformare
tutto al quadrato, usando la combinazione di put e get, dovrei accedere a tutti gli elementi del
dominio, farmi ritornare i relativi valore del codominio, trasformali al quadrato e reinserire il nuovo
valore, se tale mappa implementata con Treeset la cosa risulta molto onerosa perch ogni
operazione richiede log(n). Con Interface Entry invece tutto pi veloce e maneggevole.
Lo static davanti ad interface vuol dire che la classe con cui poi verr implementata non ha bisogno di
accedere al this della classe che sta sopra (che lo ospita). Se non ci fosse lo static la differenza sta
nell'uso della memoria: se c'e' static non ho bisogno in Entry di avere un puntatore alla classe Map che
la ospita.
voglio mettere tutti i nomi degli studenti in maiuscolo, e poich entrySet mi torna un
insieme, allora posso iterarlo. Da notare che e ora rappresenta delle coppie.
Es.:

for (Map.Entry<Integer, String> e : Studente.entrysSet())


e.setValue(e.setValue().ToUpper()); // prendo il valore

vecchio, lo faccio maiuscolo e


sostituisco questo valore nuovo a quello vecchio; e lo faccio con un for unico in temp O(n)

In alternativa potevo ad esempio scorrere sulle chiavi :


for (Integer i : Studenti.keySet())
studente.put(i, studente.get(i).ToUpper());

// richiede O(n logn) se uso un Treeset

ed molto meno efficiente


Ricapitolando, di seguito vi la interaccia nel suo complesso:
public interface Map<K,V> {
int size();
boolean isEmpty();
boolean containsKey(Object key);
boolean containsValue(Object value);
V get(Object key);
V put(K key, V value);
V remove(Object key) ;
void putAll(Map<? extends K, ? extends V> m);
void clear();
Set<K> keySet();
Collection<V> values();
static interface Entry<K,V>{
public K getKey();
public V getValue();
public V setValue(V value);
}
Set<Map.Entry<K, V>> entrySet();
}

Questa interfaccia ha i seguenti metodi

V put(K key, V value): inserisce in una mappa il valore fornito dal parametro value

associandogli la relativa chiave fornita dal parametro key. Se la mappa contiene gi un valore
per la chiave key, allora lo stesso sar sostituito dal nuovo valore value e il vecchio valore sar
ritornato dal metodo.
void putAll(Map<? extends K,? extends V> m): inserisce in una mappa tutte le coppie
chiave/valore della mappa fornita dal parametro m.
V get(Object key): ritorna il valore associato dalla chiave indicata dal parametro key. Se la
chiave non presente sar ritornato il valore null.
V remove(Object key): rimuove lassociazione della chiave di cui il parametro key ritornando il
relativo valore oppure il valore null se la chiave non presente.
void clear(): rimuove da una mappa tutte le coppie chiave/valore.
int size(): ritorna la quantit di coppie chiave/valore presenti in una mappa.
boolean containsKey(Object key): verifica se una mappa contiene la chiave indicata dal
parametro key.
boolean containsValue(Object value): verifica se una mappa contiene il valore indicato dal
parametro value.
boolean isEmpty(): verifica se una mappa vuota.
Collection<V> values(): ritorna una collezione di tipo Collection di tutti i valori di una mappa.
Qualunque cambiamento effettuato sulla vista ritornata influenzer la relativa mappa e
viceversa.
Set<Map.Entry<K,V>> entrySet(): ritorna una collezione di tipo Set di tutte le coppie

Set<K> keySet()

ritorna una collezione di tipo Set di tutte le chiavi di una mappa.


Qualunque cambiamento effettuato sulla vista ritornata influenzer la relativa mappa e
viceversa.
Il tipo Entry<K, V> uninterfaccia definita allinterno dellinterfaccia Map che astrae il
concetto di coppia chiave/valore. Dispone dei seguenti metodi: getKey per ottenere la
chiave dellentry, getValue per ottenere il valore dellentry e setValue che consente di
modificare, con un nuovo valore, il valore dellattuale entry.

Map pu essere implementato con:

HashMap: tabella hash O(1)


Hashtable: (gestisce concorrenza)
IdentityHashMap: usa == invece di equals
LinkedHashMap: HashMap+LinkedList
TreeMap: log(n)
WeakHashMap: usa == e non trattiene i valori quando le chiavi non sono pi usate

Collections
java.util.Collection interfaccia
java.util.Collections classe di utilit:
sort
reverse
shuffle
sort
binarySearch
<T> void copy(List<? super T> dest, List<? extends T> src)

Convenzione lettere tipi paramentrici

E: Tipo degli elementi in una collezione


x: Tipo delle chiavi in una mappa
V:
Tipo dei valori in una mappa
T:
Tipo generico
S,U: Altri tipi generici

Notazione generics

<T> <?> <K,V> <T,S> <?,?> etc.


<T extends x> <? extends x>
<T super x> <? super x>
<T extends y&z> <? extends y&z>
Dove: x pu essere interfaccia, classe o un'altra variabile di tipo
y pu essere interfaccia o una classe
z pu essere interfaccia

Mutable - immutable:

in generale gli oggetti sono modificabili o immodificabili.


L'oggetto Date ad esempio appena creato si pu modificare. Anche le collecion e le mappe (e
tutti i loro sottogruppi) sono modificali.
Le collezioni viste fin'ora sono mutabili ovvero ne possiamo modificare il contenuto dopo
averle create:
es. la lista la creo vuota o poi ci aggiungo gli elementi:
List<Integer> l = new LinkedList<>();
l.add(1);//modifica
l.add(2);//modifica
l.remove(3);//modifica
int i = l.size()//non modifica

Modificare vuol dire cambiare i valori della variabili interne.


Immutabili vuol dire che una volta che l'oggetto creato, non pu essere pi modficato.
Per le collezioni e in generale per gli oggetti possibile definirli in modo che siano immutabili
ovvero non modificabili dopo la creazione.
Java definisce gi oggetti immutabili: String, Integer, Boolean, etc. cio gli oggetti che
chiamiamo Wrapper. In JAva non si cambia il valore delle stringhe.
Altri li possiamo definire noi.
A cosa pu servire un oggetto immutabile?

1) Semplifica la gestione concorrente (+Thread)


2) Evita la modifica non autorizzata delle classi
Per approndimento, vedi post: http://cosenonjaviste.it/le-classi-immutabili-in-java/

Esempio: String
Gli oggetti String dopo la creazione non si cambiano.
Metodo toLower della classe String
Firma: public String toLower();

In pratica invece di modificare l'oggetto this cambiandone il contentuto, si lascia inalterato e


si ritorna una nuova stringa il cui contentuo il contuto di this trasformato in minuscolo.
Es:
String l = "Miao".toLower();
l.equals("miao")==true;
Vediamo ad esempio questo codice da un punto di vista grafico

String s = "a"+nome;
s = s + "c";// questa

sar una nuova stringa e di conseguenza il vecchio puntatore cambiato


con un nuovo puntatore ad un oggetto che sar sempre di tipo String che per contiene una
stringa diversa dalla prima; il "+" quindi crea qualcosa di diverso

Nell'esempio sopra la memoria dell'oggetto (la variabile a) viene riusata, gli immutable invece sono
usa e getta (nell'esempio la stringa creata prima, viene buttata via dopo l'uso del "+"). Tutti i metodi
che cambiano una stringa, in realt, creano una nuova stringa con le varie modifiche e quella vecchia
viene buttata via.
Stessa cosa vale per gli Integer.
Svantaggi oggetti immutabii:
- usano un po' troppa memoria perch per ogni operazione si crea un nuovo oggetto
Se vogliamo usare le stringhe senza che ogni volta venga creata una nuova copia ci sono delle classi
apposite in java (cio la versione "mutabile" di String) che sono:
- String Builder -> si usa con un unico thread (no concorrenza)
- String Buffer -> si usa in presenza di pi Thread che accedono allo stesso oggetto
Le Collection sono mutabile ed esiste per tutte le collection una versione immutabile delle stesse.
Ora se ho uno spreco di memoria con un oggetto immutabile, perch usare lo stesso concetto anche
sulle collection che sono una collezione di oggetti che poi dopo il loro uso devo buttare via? Le cose
immutabile creano molti meno effetti indesiderati ed errori ed evita che qualcuno dal di fuori vada a
modifica loggetto su cui lavoriamo.
Ad esempio stiamo lavorando sulla class Person, dove dichiator le variabili di tipo private e non metto
alcun tipo di metodo set, cos nessuno dal di fuori della classe pu modificare le variabili
Class Persona{
private String nome;
private Date nascita; //dal di fuori pu essere modificata?
String get.Nome();
Date get.Nascita();
...
}

Se prendo "nome" essendo di tipo String immutabile e se vado a cambiare il nome, in realt creo una
copia modificata dell'oggetto originale e quindi un nuovo oggetto, e l'oggetto originale rimane
invariato.
Ad esempio con il seguente codice il metodo toUpper() si crea una copia temporanea del nome, fa le
modifiche e poi il tutto va in un nuovo oggetto, perch appunto non c'e' il metodo "set" .
UsoPersona{
p.getNome().toUpper();
p.get.Nascita.add(....):
...
}

Ma osserviamo che se invece prendiamo la variabile nascita, essa di tipo Date che mutatile e
quindi dal di fuori anche senza set, potrebbe essere modificata a mia insaputa.
Vantaggi uso oggetti immutabili
- l'uso di oggetti immutabili garantisce che non possano essere fatte modifiche dall'esterno.
- semplificano la gestione concorrente in presenza di pi Thread (se l'oggetto immutabile non ci
sono problemi di stato non consistente dell'oggetto stesso)
I mutabili sfruttano meglio la memoria e vanno in bene in contesti single thread (non ci sono modifiche
contemporanee)
Gli immutabili sfruttano pi memoria e sono buoni in contesti concorrenti o quando ci sono problemi di
consistenza dei dati.

Come facciamo a definire una classe immutabile:


Senza metodi che la modificano (solo get)
Variabili d'istanza private (in modo che neanche le sottoclassi possano rompere il vincolo) e
final in modo che il compilatore controlli che anche la stessa classe non le modifichi
accidentalmente
Copiare i parametri mutabili del costruttore
Copiare i riferimenti mutabili ritornati dai metodi
Classe final (strong) o tutti metodi final (weak)
Come si pu creare una Collezione immutabile? Ad esempio una lista che sia immutabile? e i suoi
metodi? Dovrebbe essere definita a spanne cos:
public class Lista<T> implements Iterable<T>{
private final T info;
private final Lista<T> next;
public Lista<T> vuota();
public Lista<T> concatenate(Lista<T> t);
public T getHead();//attenzione
private Lista<T>(){};
public int size();
public Itertor<T> iterator();//attenzione
...

Vediamo le cose nel particolare e iniziamo dalle dichiarazione delle variabili e dei costruttori:
public class Lista<E> implements Iterable<E>{ //su iterable non dobbiamo implementare il

//metodo remove altrimenti andiamo a modificare la lista, ma remove

opzionale
//per le variabili di istanza vi final il che vuol dire che dopo aver messo i valori a info e next, questi
non possono
//essere pi modificati
private final E info;
private final Lista<E> next;

//creiamo due tipi di liste, ma quella vuota per ora la tralasciamo


Lista<E>() {}; //serve per creare una lista vuota

//questo costruttore crea una lista a partire dal puntatore al prossimo elemento

private Lista<E> (E info, Lista<E> next) {


this.info = info;
this.next = next;

.....

Come facciamo ora a dichiarare i metodi che devono anche loro seguire la filosofia "immutabili"?
Facciamo ad esempio il metodo concatena, e so che non posso modificare il this. Concatena prende il
this, prende la lista che gli passo come argomento e mi torna una nuova lista che l'unione delle due.
Se avessi avuto una lista mutabile, questo metodo "concatena" sarebbe stato il metodo addAll, ma qui
siamo in presenza di qualcosa di diverso.
Vediamo come la situazione graficamente (supponiamo siano liste di interi):
- da una parte ho il mio this fatto dei info e next, a suo volta next punta ad una info con associato il
relativo next, fino all'ultimo elemento che contiene 3 e il cui puntatore vuoto:

- dall'altra parte ho la lista l che voglio aggiungere e che passo come argomento

- alla fine devo costruire una lista che chiamo ret che sia "l'assemblaggio" delle due" e so che non
posso modificare nessuno dei valori (perch sono stati dichiarati final); sostanzialmente devo
costruirmi una copia:

Quindi devo procedere a fare una copia, ma poich entrambe le liste sono immutabili posso evitare di
fare la copia "pi completa" perch invece di fare due "oggetti nuovi", posso usare l'oggetto l che gi
c'e', perch tanto non lo modifico, esso rimane tale e quale e quindi invece di duplicare tutto si fa un
qualcosa di questo tipo:

cio della prima parte faccio una copia, della seconda parte non occorre fare una copia. Alla fine
dobbiamo implementare il seguente algoritmo: duplichiamo la prima parte fino al null e invece di
creare il null nell'ultimo elemento, al suo posto ci mettiamo la lista l in modo che si concateni.
Quindi il codice del metodo sar qualcosa del tipo:
public Lista<E> Concatena (Lista<? extends E> l){
Lista<E> ret {
if (next!=null)
return new Lista(info, next.Concatena(l));//chiamata ricorsiva che fatto sul

//this (cio il target) e non sull'argomento passato

else
}

return new Lista(info, l);

In questo modo le liste che si creano sono immutabili.


Ma abbiamo un'altra difficolt: come creare la copia; per farlo ho bisogno di altri metodi di supporto.
--inizio lezione del pomeriggio-Altri metodi che potrebbero essere implementati in filosofia "immutabili" che non modificano quindi la
lista?
- E getFirst(); torna il primo elemento della lista
- E get(int i); torna l'i-simo elemento della lista
- int Size(); torna la dimensione della lista
- Iterator <E> iterator(); torna l'iteratore; l'iterator ha 3 metodi: hasNext(), next() e remove()
(quest'ultimo non va quasi mai implementato; se va implementato in presenza di collezione
immutabile bisogna che torni/lanci l'eccezione in modo tale che sia esplicito che l'applicazione che
abbiamo scritto non riesca a togliere l'elemento. I primi due metodi in presenza di collezione
immutabili si implementano in modo standard)
Quindi abbiamo visto come si concatenano due liste immutabili, ma come faccio a creare una lista?
Prima abbiamo visto che il costruttore privato, in realt questo tipo di strutture dati viene
implementato di solito con l'utilizzo di una convenzione che si chiama Design Pattern Null Object.
L'idea di base implementare la stessa interfaccia con due classi: una classe che si occupa di quando
la "cosa" vuota (con i vari metodi) e una classe che si occupa di quando una cosa "non vuota" e
quest'ultima richiama dentro di essa e nei metodi le cose definite nella classe "vuota".
" il Null Object una tecnica che suddivide l'implementazione di una collezione tra collezione senza
elementi e collezione con elementi. Dal punto di vista esterno questa suddivisione non si nota, perch
sia la collezione vuota che quella con elementi implementano un'interfaccia comune. La ragione della
suddivizione di rendere pi semplice l'implementazione dei vari metodi".
Le liste, gli alberi, gli insiemi e la maggior parte delle strutture dati hanno un tipo particolare di
oggetto che quello vuoto (infatti abbiamo anche il metodo isEmpty() che pu essere true o false).
Quindi invece di avere l'interfaccia List<E> implementata con LinkedList<E> o ArrayList<E> o altro
tipo modificabile, quando si adotta il Null Object con gli immutabili (vogliamo collezioni immutabili)
definiti dal programmatore si ha una situazione del tipo: ho una interfaccia (ad esempio List<E>) e poi
ho due implementazioni di questa interfaccia:
- Null+nome interfaccia: nel nostro esempio si chiamer NullList, che servir a rappresentare e solo
quando la lista vuota
- NotNull+nome interfaccia: nel nostro esempio si chiamar NotNullList; di solito chiamata NotNull...,
ma pu prendere anche altri nomi.
Perch fare questa differenza? Perch la classe NullList (scrivo NullList in riferimento degli esempi
successivi, ma avremo NullQueue e il rispettivo NotNullQueue, ecc) e i suoi metodi sono facili da da
implementare, ad esempio;
class NullList<E> implements List<E>
public boolean isEmpty(){
return true;

}
public int Size(){
return 0; // siamo nella NullList
}
......

//l'itetatore ritorna un insieme vuoto

Poi c'e' la classe NotNulllList in cui i metodi sono implementati in modo opportuno
Class NotNullList<E> implements List<E> {
E info;
Lista<E> next;
.......
}

Questo pattern un modo per risolvere problemi che viene molto usato e (nel nostro esempio) la
stessa interfaccia Lista implementata da due classi: una classe che serve a rappresentare la lista
vuota e una classe che rappresenta una lista non vuota. L'implementazione della classe della lista
semplice, perch in pratica non fa nulla; mentre l'implementazione della lista non vuota pi
complessa. In tale visione non si definisce neanche il metodo Concatena visto prima, cio non
verrebbe fatto nel modo detto prima , ma quando dobbiamo costruire una lista, ad esempio definisco
una lista con 1 elemento
Lista<Integer> l = new NotNullList(1, new NullList()); //passo comeprimo parametro

il

//primo dato (ad esempio un numero) e come secondo parametro "next" passo un
elemento nullo)
Questo un modo diverso di affrontare il problema delle collezioni ed orientata pi verso la
programmazione funzionale, e qua gli effetti collaterali non ci sono.
Se dovessi fare una lista con due elementi:
di prima

Lista<Integer> l2 = new NotNullList (2, l); // come secondo parametro passo la lista

//che aveva come unico elemento il numero 1 e ora l2 ha come


contenuto: {2,1}
Questo stile di programmazione evita alcuni problemi: nel metodo Concatena di prima abbiamo dovuto
distinguere fra "next uguale a null" e "next diverso da null". Se facciamo il Concatena ora, devo
definire il Concatena sia per una lista nulla e sia per una non nulla, e se ho una lista nulla e ci
concateno una lista non nulla, molto semplice:
- metodo Concatena per NullList: mettere davanti ad una lista, la lista nulla, oppure concatenare una
lista nulla con un'altra nulla come avere solo la lista originaria, perch appunto la lista nulla non ha
niente al suo interno:
Lista<E> Concatena (Lista <? extends E> l){ //versione per NullList, e lo uso solo per NullList
return l;
}

- metodo Concatena per NotNullList:


Lista<E> Concatena (Lista <? extends E> l){
return new NotNullList(info, next.Concatena(l)); //info la informazione non nulla, e

con next

//faccio quello che facevo prima con il metodo Concatena, ma non mi devo pi
preoccupare se next
//nullo perch in ogni caso l'ultimo elemento non nullo, ma un oggetto NullList,
e quindi nell'ultimo
//passo viene invocato il Concatena della versione NullList

}
In questo modo sono stati semplificati notevolmente i metodi al costo di aggiungere una classe;
questa cosa stata fatta col polimorfismo: aggiungendo classi abbiamo metodi pi semplici. Quindi se
voglio sfruttare il poliformismo anche nella gestione di questi oggetti, la versione immutabile
dell'oggetto semplifica molto i metodi, perch ogni volta mi concentro su un caso pi piccolo e invece
di pensare a tutte le liste penso a cosa accade nella lista nulla e a cosa succede nella lista non nulla e
ho i metodi pi semplici.
Queste stesse capitano se penso al metodo Size() (nella versione NotNullList)
int Size() {
return 1 + next.Size();
}

Se next.Size() NotNull allora avr anche lui dentro di se in modo ricorsivo questa chiamata finch
l'ultimo elemento sar Null che ovviamente mi ritorner 0 e tutto fatto senza il costrutto if... Se
dovessi fare la stessa cosa per una lista che nulla o non nulla, la cosa pi complessa.
In questa filosofia il costruttore sar solo nella classe non vuota ed esso non sar mai vuoto: nel caso
NonNullList il costruttore (non mai vuoto) e come parametri deve avere la info e il next (mentre
nella filosofia di prima dovevo avere sia il costruttore per la lista vuota che non vuota):
NotNullList(E Info, Lista<? extends E> next){
this.info= info;
this.next= next;
}

Implementare i metodi con questa logica li rende pi corti e semplici.


Questo modo di programmare aiuta a concentrarsi nei casi limite.

Grafica (come i linguaggi ad oggetti gestiscono la grafica), il codice sar poi messo online (da
visionare e provare: lezione21.zip)
All'inizio Java aveva una libreria grafica denominata awt (applet da girare su un browser, ed era una
libreria limitata), poi arriv la libreria swing che era pi avanvata (in swing
abbiamo thread in esecuzione in parallelo)
L'interfaccia grafica rapprensentata da una finestrella con pulsanti, riga dove
possiamo inserire dellinput, immagini ecc:

Normalmente sappiamo che per eseguire un programma java abbiamo il main() come punto di
partenza; nella grafica invece, ed inoltre, abbiamo un qualcosa di nuovo che sono gli eventi (l'utente
clicka da qualche parte e il programma fa qualcosa in base al codice scritto) e vi la presenza di pi
thread, in totale 3, di cui i due pi importanti sono:

- un thread di inizializzazione, che chiamato nel main, che parte e "disegna la finestrella, i pulsanti, il
checkbox ecc" e poi passa al thread secondario il testimone (tale thread pu poi morire o continuare a
fare altre cose); qui il programmatore perde il controllo di quello che fa l'utente.
- un thread secondario quello dello Swing, il quale "schiavo" dell'utente, cio l'utente usa il mouse
per selezionare o clickare, questo genera una azione che viene gestita dal thread con una libreria gi
pronta chiamata Swing, con del codice gi scritto e che non si pu modificare.
Con la parte grafica di java, entra in gioco la programmazione ad aventi. Un evento un qualcosa che
accade all'interno di una finestrella grafica, ad esempio vediamo la pressione di un determinato
pulsante OK: il codice viene eseguito alla pressione usando un oggetto che si chiama Listener (che
implementa una certa interfaccia) e che in qualche modo viene messo "in registrazione" al pulsante
OK, di norma attraverso una classe anonima (e avremo un listener per un primo pulsante, un secondo
listener diverso per un secondo pulsante e cos via); dopo che avvenuta la registrazione, ogni volta
che l'utente preme il pulsante, automaticamente Swing invoca quel metodo implementato
dall'interfaccia Listener; quando abbiamo finito ritorna a fare le sue cose.
Passi:
1. il pulsante premuto
2. Swing ( un thread parallelo) visualizza l'animazione della pressione
3. chiama il metodo onAction del Listener (vi sono tanti altri metodi, onAction solo un esempio)
4. scriviamo le istruzioni che vogliamo che vengano fatte, quando il metodo finisce, prosegue il thread
dello swing
Fino al punto 3 lo swing rimane bloccato e dobbiamo gestire questa cosa, soprattutto se l'esecuzione
delle istruzioni richiede molto tempo (in questo caso, dobbiamo creare un altro thread che le gestisca,
altrimenti la parte grafica rimane bloccata troppo a lungo, ed una cosa brutta da vedere).
L'inizializzazione della parte grafica pu essere fatta nel main o in una classe a parte
//init

titolo

public stati void main (....){


final JFrame frame = new JFrame(); //creo la finestrella (contenitore) e ci do un

avere una

frame.setTitle("Test");
JButton click = new JButton("ok); //aggiungo il pulsante che chiamo Click
frame.setLayout(new FlowLayout());//aggiundo un layout al frame in modo da

//disposizione estitica migliore dei vari oggetti


// Il FlowLayout permette di disporre i componenti di un contenitore da sinistra verso destra su
ununica linea.
frame.add(click); // aggiungo il pulsante alla finestrella
final JTextField() nome = new JTextField("nome");
final JTextField() numero = new JTextField("secondi");
frame.add(nome);
frame.add(numero);
... problema 1
... problema 2

a questo

frame.setVisibile(true); // appare la finestra e il thread Swing vive di vita propria;

//punto main pu anche finire, ma la finestra rimane visibile e attiva


Problema 1: premo il pulsante ok e non capita nulla, perch non ho fatto la "registrazione". Quindi
devo creare Listener e far si che il pulsante ok faccia qualcosa
ActionListener listener = new ActionListener { //uso una classe anonima
@override
public void actionPerformer(ActionEvent e){

//actionPerformer l'unico metodo di ActionListener; uso e per capire quale

stato
//l'evento che ha innescato la chiamata (in questo caso sappiamo che
innescato dalla
//pressione del pulsante OK; in presenza di pi listener, sar e a dirci cosa ha
fatto cominciare

//l'evento
//inizio istruzioni che vogliamo siano eseguite in seguito all'evento
nome.setText(nome.getText().toUpper());

//e se il codice che scrivo dopo di veloce esecuzione va bene, altrimenti


dopo veloce va
//bene, altrimenti devo mettere il codice che gestisce le cose in un altro
thread
......
......
}
} //tra tutti gli ascoltatori che vi sono, aggiungo anche questo che ho appena
creato per il pulsante OK
//quindi ora alla pressione del codice OK vengono eseguite le istruzioni scritte
qua sopra
click.addAcionListener(listener);

Problema 2: come si chiude la finestrella?


// nel codice, prima di "visible" si mette questa istruzione per uscire, altrimenti il
processo Swing rimane
// in esecuzione
frame.setDefaultCloseOperation(javax.swing.WindowsConstants.EXIT_ON_CLOSE);

Ora fintanto che facciamo istruzioni veloci (qualche millisecondo) possiamo gestire l'applicazione come
abbiamo visto.
Di seguito vi un esempio di applicazione non veloce e vediamo come gestirla con la parte grafica;
supponiamo che quando premiamo un tasto, viene emesso un suono, il thread rimane fermo per un
tot di secondi e poi si riemette un suono

se

static void longOperation (int secondi){


java.awt.Toolkit.getDefaultToolkit().beep(); //fa un suono
thread.sleep(secondi * 1000); //sleep richiede millisecondi come arg. quindi

//vogliamo secondi, dobbiamo moltiplicare


per 1000
//qua vi sar una eccezione da inserire per gestire sleep (dobbiamo inserirla
noi)
java.awt.Toolkit.getDefaultToolkit().beep();//fa un suono per dire che

//terminata l'operazione "lunga"

ira, per far funzionare longOperation, Il seguente codice va messo dentro actionPerformer:
{
//leggo un numero che l'utente ha inserito e lo trasformo da testo a numero
int sec = Integer.parseInt(numero.getText());

//il thread si ferma per tot secondi, e qualsiasi operazione che vada a fare
nella finestrella in
//questi secondi, non ha alcune effetto
longOperation(sec))

Poich longOperation potrebbe richiede del tempo e per quel tempo l'interfaccia non utilizzabile ,
allora meglio mettere longOperation in un altro thread che va in parallelo allo swing e perci uso
una struttura detta SwingWorker, fatta apposta per questo, che esiste gi in java: facciamo tutto
dentro il codice che stiamo scrivendo, ma usando una classe anonima.
//da notare che ora uso una classe anonima
SwingWorker worker = new SwingWorker<Void, Void>() {
@Override //il metodo invocato da un thread diverso da swing
public void doInBackground() {
longOperation(sec);
return ..........
}
.........

//una volta creato il worker, lo facciamo partire con execute()


worker.execute();

Ricapitolando una volta premuto OK, viene eseguito del codice, che prepara l'input per il metodo, poi
crea l'oggetto Worker , che estende la classe SwingWorker andando a cambiare un metodo particolare
che si chiama doInBackground (che viene fatto in background su un altro thread) e quindi l'interfaccia
grafica non viene bloccata, perch il codice eseguito in un altro thread separato e parallelo a quello
di Swing. Quando il worker stato completato viene chiamato un altro metodo per segnalare la fine
del worker.
Per questioni di efficienza tutta l'interfaccia Swing delicata dal punto di vista della concorrenza (dei
thread) e questo comporta il fatto che non si accede in modo concorrente a due oggetti dell'interfaccia
Swing. Quindi: quando c'e' un actionPerformer viene eseguito dal thread Swing, e quando viene
eseguito doInBackground, quest'ultimo eseguito da un altro thread separato che appunto il worker.
Il thread Swing pu accedere a tutti i vari numero.getText, numero.setText, ecc cio a tutti gli oggetti,
perch sono cose del thread Swing; il thread worker non dovrebbe mai accedere a nessun oggetto
dell'interfaccia Swing. Per tale ragione doInBackground viene fatto su un thread a parte e questo
thread deve operare solo sui suoi dati (che non riguardano l'interfaccia).
Quando worker termina, ho un metodo "done", richiesto dallo swing, per sapere quando worker ha
terminato.
Worker ha tutta una serie di metodi che hanno lo scopo di preservare le cose
dell'interfaccia grafica.
Ci sono tutta una serie di eventi a pi basso livello (mouse clickato, pressione di un tasto, ecc) che
possono essere intercettati con dei listener diversi (ad esempio mouseListener) per gestire meglio
molti aspetti della programmazione o della gestione di possibili errori di input (ad esempio in un
campo dove ci si aspetta un numero, un utente pu inserire una stringa vuota, o del testo, ecco che
allora viene lanciata una eccezione per gestire cose anomale).