Programmazione III
Dispense
Java: eventi e GUI, pattern Observer e MVC, JavaFX,
programmazione concorrente e socket
Programmazione III · Dispense Indice
1. Architetture software 5
1.1 Modello a oggetti e UML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.2 Dai monoliti alle architetture n-tier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2. Java: compilazione e memoria 6
2.1 Dal sorgente all'esecuzione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.2 Memoria della JVM e passaggio dei parametri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
3. Ereditarietà, polimorfismo, interfacce 7
3.1 Sottoclassi e sottotipi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3.2 Tipo statico, tipo dinamico, upcasting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3.3 Overriding e super . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3.4 Binding dinamico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3.5 Downcasting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3.6 Classi astratte e interfacce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3.7 Lo schema Kernel–Modulo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
4. RTTI e Reflection 10
4.1 instanceof o getClass()? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
4.2 Reflection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
5. Input/Output 11
5.1 Stream, byte e caratteri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
5.2 Composizione di stream . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
5.3 Eccezioni e chiusura delle risorse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
5.4 Scanner e PrintWriter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
5.5 Serializzazione di oggetti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
6. Generici e collezioni 13
6.1 Tipi generici . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
6.2 Type erasure e raw type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
6.3 Collezioni e iteratori . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
6.4 Generici, sottotipi e wildcard . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
7. Classi innestate e lambda expression 15
7.1 Tipi innestati . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
7.2 Classi anonime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
7.3 Lambda expression . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
8. Eventi e interfacce grafiche con Swing 17
8.1 Programmazione guidata dagli eventi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
8.2 Swing in breve . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
8.3 Il modello a delega: sorgenti, eventi, listener . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
8.4 Adapter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2
Programmazione III · Dispense
8.5 Schemi di uso dei listener . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
8.6 Generare eventi personalizzati . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
9. Pattern Observer–Observable 20
9.1 L'API di java.util . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
9.2 Variante senza java.util.Observable (non nelle slide) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
10. Pattern MVC 22
10.1 Senza Observer: tutto è accoppiato . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
10.2 Con Observer: il modello notifica le viste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
10.3 Un ulteriore miglioramento: interfaccia per la vista . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
11. JavaFX 24
11.1 Struttura di un'applicazione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
11.2 Fogli stile CSS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
11.3 MVC con FXML: il modo di lavorare del corso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
11.4 JavaBeans e properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
11.5 Binding di properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
11.6 Collezioni osservabili e più viste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
12. Thread 27
12.1 Creare e avviare un thread . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
12.2 Ciclo di vita . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
12.3 Controllo dei thread . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
12.4 Eccezioni nei thread . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
12.5 Thread e interfacce grafiche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
13. Sincronizzazione e cooperazione 29
13.1 Interferenza e sezioni critiche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
13.2 Semaphore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
13.3 synchronized . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
13.4 Cooperazione: wait e notify . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
13.5 Deadlock e starvation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
13.6 Attenzione alla granularità . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
14. Il package java.util.concurrent 32
14.1 BlockingQueue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
14.2 Lock, ReentrantLock e Condition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
14.3 ReadWriteLock . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
15. Pipe 34
16. Task, thread pool, Callable e Future 35
16.1 Executor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
16.2 Callable e Future . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
16.3 ScheduledExecutorService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
17. Socket 36
3
Programmazione III · Dispense
17.1 Servire più client in parallelo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
17.2 Stream di oggetti sui socket . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
18. Errori tipici (da rivedere prima dell'esame) 38
4
Programmazione III · Dispense 1. Architetture software
Il corso non tratta i microservizi (li affronta TAAS in magistrale), ma insegna a strutturare le applicazioni a
componenti, anche quando poi girano tutte in una sola JVM. I vantaggi sono modularità, riuso, manutenibilità,
separation of concerns e information hiding; inoltre ci si abitua a ragionare su quali componenti lavorano in
parallelo e quali dati condividono, che è il tema dei capitoli sui thread.
1.1 Modello a oggetti e UML
L'approccio Object Oriented usa lo stesso modello in tutte le fasi: analisi (OOA), progetto (OOD) e
programmazione (OOP). Il class diagram UML è il modello OOA dell'applicazione: contiene solo gli oggetti del
dominio del problema, con le loro strutture dati e operazioni, ed è indipendente da linguaggio, interfaccia utente e
database.
1.2 Dai monoliti alle architetture n-tier
Architettura Com'è fatta Pregi e limiti
Un unico blocco di codice con interfaccia, logica e Nessuna separazione; applicazioni
Monolite gestione dei dati. Tipico dei mainframe. stand-alone.
Client/server Backend (server) con database e manipolazione dei Più scalabile (carico sui client). Ma la
dati; frontend (client) con l'interfaccia. Diffusa dalla business logic è spezzata tra client e server:
(two-tier) è difficile riusare la UI con dati diversi o lo
fine degli anni '70. stesso DB da frontend diversi.
Livello 1: dati (DBMS, XML...). Livello 2: business
Three-tier Nessun livello fa assunzioni
logic. Livello 3: interfaccia utente. Dai primi anni '90. sull'implementazione degli altri: ognuno
conosce solo come comunicare con quelli
adiacenti.
UI, presentation logic, business logic, infrastructure
n-tier Permette configurazioni diverse.
services (messaggistica, transazioni), data layer.
Vantaggi del three-tier: flessibilità (le componenti sono riusabili e modificarne una non impatta il resto, a meno di
cambi di API; i bug si cercano in modo mirato), interconnettività (N interfacce diverse sullo stesso servizio, più
applicazioni sugli stessi dati) e gestione di sistemi distribuiti (si aggiorna la logica senza toccare i client).
Nota
Three-tier è un concetto logico, non fisico: i tre livelli possono stare su tre macchine, su due o anche su una sola.
Sul piano della comunicazione tra servizi si distingue tra interazione sincrona request/response (orchestration) e
asincrona basata su eventi (choreography), tipicamente tramite un message broker (es. RabbitMQ, protocollo
AMQP). L'idea di base, componenti che non si conoscono ma si scambiano messaggi, ritorna nei listener e nel
pattern Observer. 5
Programmazione III · Dispense 2. Java: compilazione e memoria
2.1 Dal sorgente all'esecuzione
Il compilatore trasforma in bytecode ( ), eseguito da una macchina virtuale (la
MyClass.java MyClass.class
JVM) su sistemi diversi. Il garbage collector libera in background la memoria degli oggetti non più raggiungibili.
2.2 Memoria della JVM e passaggio dei parametri
La memoria è divisa in tre aree:
• memoria statica: codice delle classi, costanti e variabili (una sola copia per classe);
static
• stack: un record di attivazione (frame) per ogni invocazione di metodo, con parametri formali, variabili locali,
valore e indirizzo di ritorno. Esiste solo finché il metodo è in esecuzione;
• heap: gli oggetti creati con e gli array. Le variabili di istanza vivono qui, una copia per oggetto.
new
Il passaggio di parametri è sempre per valore. Per gli oggetti si copia il riferimento: il metodo può modificare
l'oggetto puntato (es. riempire un array ricevuto) ma non riassegnare la variabile del chiamante. Non esistono ref
e .
out
Da ricordare
Quando arriveremo ai thread: ogni thread ha il proprio stack, mentre l'heap è condiviso. È la ragione per cui
due thread possono interferire sugli stessi oggetti (cap. 12-13).
6
Programmazione III · Dispense
3. Ereditarietà, polimorfismo, interfacce
3.1 Sottoclassi e sottotipi crea una sottoclasse che eredita variabili e metodi di istanza della
class WindowWithTitle extends MyWindow
sopraclasse e può aggiungerne o ridefinirne. L'ereditarietà realizza la relazione is-a: un rettangolo è un poligono.
In Java l'ereditarietà è singola (albero con radice ; i tipi primitivi non sono ); l'ereditarietà multipla è
Object Object
sostituita dalle interfacce.
3.2 Tipo statico, tipo dinamico, upcasting
Il type checking statico lo fa il compilatore, guardando i tipi dichiarati. Il tipo reale di un oggetto si conosce solo a
runtime. Il polimorfismo è la proprietà di un oggetto di avere più tipi lungo la gerarchia: l'assegnamento x = expr
è legale se il tipo di è uguale a quello di o ne è un sottotipo. Spostarsi verso un supertipo (upcasting) è
expr x
sempre sicuro, perché la sottoclasse ha tutti i metodi della sopraclasse.
MyPolygon p = new MyRectangle(); // upcasting
p.draw(); // ok: draw() esiste in MyPolygon
p.diagonal(); // ERRORE di compilazione: il tipo dichiarato non ha diagonal()
((MyRectangle) p).diagonal(); // ok con un downcast (verificato a runtime)
3.3 Overriding e super
Una sottoclasse può ridefinire (override) un metodo; con ne riusa la versione della sopraclasse
super.metodo()
per estenderla invece di sostituirla, e con invoca il costruttore. I campi non sono accessibili
super(...) private
alla sottoclasse: si passa dai metodi pubblici.
class Cell {
private int content = 0;
public int get() { return content; }
public void set(int n) { content = n; }
}
class CellWithMemory extends Cell {
private int backup = 0;
@Override public void set(int n) { backup = get(); super.set(n); }
public void restore() { super.set(backup); }
}
3.4 Binding dinamico
Nei linguaggi a oggetti la chiamata di un metodo è risolta a runtime in base al tipo reale dell'oggetto. Vale solo
per i metodi: l'accesso ai campi è risolto staticamente dal tipo dichiarato, e un campo con lo stesso nome in una
sottoclasse nasconde quello della sopraclasse senza ridefinirlo.
class A {
String type = "A";
String getType() { return type; }
void printViaMethod() { System.out.println(getType()); }
void printViaField() { System.out.println(type); }
}
class B extends A {
String type = "B";
@Override String getType() { return type; }
}
B b = new B();
b.printViaMethod(); // B : getType() è dinamico, esegue la versione di B
b.printViaField(); // A : il campo è risolto in A, dove è scritto il codice
System.out.println(b.type); // B
Il binding dinamico sostituisce gli sul tipo: invece di una procedura che considera ogni forma, si ridefinisce
switch
in ogni sottoclasse e il codice chiamante resta immutato quando si aggiunge una forma.
draw() 7
Programmazione III · Dispense
abstract class MyShape { abstract void draw(); }
class MyRectangle extends MyShape { void draw() { /* ... */ } }
class Figure {
MyShape[] shapes;
void drawFigure() { for (MyShape s : shapes) s.draw(); } // aggiungere MyTriangle non cambia Figure
}
3.5 Downcasting
Il downcast va da un tipo generale a uno specifico: . Il compilatore lo accetta se è plausibile; a runtime la
(C) obj
JVM verifica che l'oggetto sia davvero un (o un suo sottotipo) e altrimenti lancia . Con
C ClassCastException
classi e interfacce il compilatore è più permissivo: accetta il cast se esiste un possibile sottotipo comune ai due
tipi. Per prevenire l'eccezione si verifica prima il tipo, senza abusarne (spesso il polimorfismo evita il cast).
Shape f = ...;
if (f instanceof Circle) { Circle c = (Circle) f; c.op(); }
3.6 Classi astratte e interfacce
Una classe astratta ha almeno un metodo (senza corpo): può avere costruttore, campi e metodi
abstract
concreti ma non si può istanziare con . Un'interfaccia contiene solo la firma dei metodi, cioè un contratto, e
new
non è vincolata alla struttura ad albero.
Classe astratta Interfaccia
extends implements A, B
Ereditarietà una sola ( ) quante si vuole ( )
Contenuto campi, costruttori, metodi concreti e astratti firme dei metodi (il contratto)
contratto implementabile da classi di gerarchie
Quando base comune con codice condiviso diverse
Un oggetto visto tramite un'interfaccia mostra solo i metodi di quell'interfaccia ( non permette
A a = new C();
, a meno di un downcast ). Un esempio di libreria è , che permette a
a.metodB() ((C) a).metodB() Comparable
di ordinare qualunque classe implementi :
Arrays.sort compareTo
class MyRectangle extends MyPolygon implements Comparable<MyRectangle> {
int height, width;
public int compareTo(MyRectangle r) { return height - r.height; }
}
MyRectangle[] a = new MyRectangle[10];
Arrays.sort(a); // ordina usando compareTo() degli elementi
3.7 Lo schema Kernel–Modulo
Combinando interfacce, classi che le implementano e relazioni di contenimento si ottiene lo schema alla base dei
pattern architetturali del corso. Una classe kernel usa un componente di tipo interfaccia modulo (in upcasting) e
ne invoca i metodi; le implementazioni del modulo sono scritte a parte. Grazie a polimorfismo e binding
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.