Lezione 2 · Canale 1 · venerdì 25 settembre 2026
Associazioni e collezioni in Java
Progettazione del Software
Collegare persone e automobili
Di una persona interessano nome, cognome e anno di nascita; di un'automobile targa e numero di telaio. Come si ottengono tutte le automobili di una persona?
Si potrebbero scorrere tutte le automobili create e controllare il proprietario di ciascuna, ma con migliaia di automobili è scomodo. Conviene che Persona conservi la collezione delle sue automobili e che ogni Automobile conservi il riferimento al proprietario.
Lo schema UML dei dati
Prima di scrivere le classi si descrive quali dati servono e come sono collegati. Sono i dati richiesti dall'applicazione, non tutti quelli che persone e automobili hanno nella realtà (cilindrata, stipendio, stato civile).
- Ogni classe è un rettangolo: in alto il nome, sotto gli attributi, cioè i dati semplici.
- Le automobili di una persona e il proprietario di un'automobile non sono attributi. Li rappresenta l'associazione, la linea fra i rettangoli: ripeterli negli attributi sarebbe ridondante.
- La molteplicità sta agli estremi della linea. Il numero accanto a una classe dice quante istanze di quella classe sono legate a ogni istanza dell'altra.
Nello schema 1..1 accanto a Persona significa che ogni automobile ha esattamente un proprietario; 0..* accanto a Automobile che una persona possiede da zero a un numero qualsiasi di automobili.
*è0..*, nessun vincolo: si può omettere.1è1..1, esattamente uno.0..1è al più uno: il proprietario può mancare. È il caso del codice sotto, dove l'automobile viene creata senza proprietario.- In generale
x..y: almenox, al piùy.
Lo schema non riguarda Java: si può disegnare prima del programma. Fa capire subito a chi non conosce il problema quali dati ci sono e come si legano.
Le classi Java
Gli attributi diventano campi. L'associazione diventa una collezione in Persona e un riferimento in Automobile. Non è una gerarchia fra classi: è un legame fra oggetti.
import java.util.*;
class Persona {
String nome;
String cognome;
int nascita;
HashSet<Automobile> auto;
Persona(String n, String c, int a) {
nome = n;
cognome = c;
nascita = a;
auto = new HashSet<Automobile>();
}
}
class Automobile {
String targa;
String telaio;
Persona proprietario;
Automobile(String t, String f) {
targa = t;
telaio = f;
}
}
HashSetsta nella libreriajava.util: serve l'import.- Dichiarare
autocrea solo la variabile. L'insieme, vuoto, lo creanew. - Il costruttore è il metodo con lo stesso nome della classe. Quello di
Automobilenon riceve il proprietario, che si assegna dopo:
Persona p1 = new Persona("nome", "cognome", 2000);
Automobile a = new Automobile("targa", "telaio");
p1.auto.add(a);
a.proprietario = p1;
Le ultime due istruzioni dicono un solo fatto, nei due versi.
Riferimenti e oggetti in memoria
- Una variabile di tipo classe contiene un riferimento a un oggetto, non l'oggetto. In C la differenza fra
structestruct *si scrive; in Java vale sempre. Solo i tipi primitivi, comeint, contengono il valore. newcrea l'oggetto, comemallocin C, e il suo riferimento va nella variabile: nell'assegnazione si esegue prima la parte destra. Il programma non maneggia l'indirizzo.nullè il riferimento a nessun oggetto. Un campo di tipo classe parte danull; una variabile locale non ha valore finché non si assegna, e il compilatore ne rifiuta la lettura.- Anche un
HashSetè un oggetto:autoè un riferimento, e l'insieme contiene riferimenti alle automobili, non copie. - Più variabili possono riferire lo stesso oggetto:
p1ea.proprietario. - Non esiste
free: un oggetto che nessuno riferisce più lo recupera il garbage collector.
Dopo le istruzioni sopra c'è un solo oggetto Persona e un solo oggetto Automobile.
Coerenza dei due versi
Quando l'associazione è memorizzata nei due versi, i collegamenti devono dire la stessa cosa: se a.proprietario è p1, allora a sta in p1.auto.
Se un'automobile passa da p1 a p2 servono tre passi: cambiare il proprietario di a, toglierla dall'insieme di p1, aggiungerla a quello di p2.
a.proprietario = p2;
p1.auto.remove(a);
p2.auto.add(a);
Se ne manca uno i dati si contraddicono:
aha come proprietariop2ma non è fra le automobili dip2;aè ancora fra le automobili dip1ma non gli appartiene.
Due strategie:
- fare gli aggiornamenti dei due collegamenti solo insieme, nello stesso metodo;
- lasciare gli aggiornamenti liberi e controllare la coerenza quando si interrogano i dati.
Senza private e public, che arrivano più avanti, nulla impedisce di cambiare un solo campo: per ora i due collegamenti si aggiornano sempre insieme.
I due versi servono solo se l'applicazione deve percorrere l'associazione in entrambe le direzioni. Se da una persona basta risalire alla nazione e non serve elencare gli abitanti di una nazione, il collegamento è in un verso solo. Non c'è allora nulla da tenere coerente, e non si memorizzano milioni di collegamenti per nazione.
Insiemi e liste
HashSet: l'ordine degli elementi non è garantito. La stampa può sembrare rispettare l'inserimento, ma non ci si può contare. Non esiste un «secondo elemento» e non c'è accesso per indice.- Lista: gli elementi si aggiungono in coda e hanno indice da
0.l.get(1)è il secondo elemento;l[1]non compila, perchélè un oggetto e non un array. - Una lista permette di inserire in posizione intermedia, cosa che un array non fa:
l.add(1, "nuovo")mette"nuovo"all'indice1e sposta di uno gli elementi che seguivano. - Per scorrere un insieme si usa un ciclo sugli elementi, uno alla volta; lo stesso ciclo vale per le liste.
List<String> l = new LinkedList<String>();
l.add("x");
l.add("y"); // x, y
l.add(1, "nuovo"); // x, nuovo, y
System.out.println(l.get(1)); // nuovo
for (String s : l)
System.out.println(s.length());
Esercizio: la stringa più lunga
Scrivere un metodo che, data una collezione di stringhe (insieme o lista), restituisce la stringa più lunga, non la sua lunghezza.
static String piuLunga(Collection<String> c) {
String max = "";
for (String s : c)
if (s.length() > max.length())
max = s;
return max;
}
- Si conserva la stringa più lunga trovata finora: la lunghezza non serve salvarla, è
max.length(). maxparte da""perché una variabile locale deve avere un valore prima dell'uso. La stringa vuota non ènull.Collection<String>accetta sia unHashSetsia una lista.
Tipi primitivi nelle collezioni
Una collezione contiene riferimenti a oggetti, un int è un valore. Per questo List<int> non compila: si usa la classe wrapper del tipo, Integer.
List<Integer> l = new LinkedList<Integer>();
l.add(3);
l.add(8);
l.add(2);
Per ogni add Java mette il numero in un oggetto Integer e la lista conserva il suo riferimento.
Ogni tipo primitivo ha il suo wrapper:
int:Integer;char:Character;boolean:Boolean;double:Double.
Formulario
Molteplicità
- il numero accanto a una classe: quante sue istanze per ognuna dell'altra
*è0..*1è1..10..1: al più unox..y: daxay
Riferimenti
- variabile di classe: riferimento
int: valorenull: nessun oggettonewcrea l'oggetto- niente
free: lo fa il garbage collector
Due versi
- se
a.proprietarioèp1, alloraasta inp1.auto - si aggiornano insieme, nello stesso metodo
p.auto.add(a);
a.proprietario = p;
Collezioni
HashSet: niente ordine, niente indice- lista: indici da
0 l.get(i)l.add(i, x)
for (T x : c)
...
Wrapper
List<int>: erroreList<Integer>: correttoint:Integerchar:Characterboolean:Boolean