Lezione 3 · Canale 1 · lunedì 28 settembre 2026
Sottoinsiemi e classi in Java
Progettazione del Software
Classi come insiemi
Una classe rappresenta un insieme di oggetti omogenei, le sue istanze, con i dati che li descrivono. Persona è l'insieme delle persone, con nome e anno di nascita. Studente è l'insieme degli studenti: hanno nome, anno di nascita e in più la matricola. I campi sono public per tenere gli esempi corti.
public class Persona {
public String nome;
public int anno;
}
public class Studente {
public String nome;
public int anno;
public int matricola;
}
Scritte così, le due classi non hanno alcun legame: per Java sono due insiemi separati, anche se due campi coincidono. Una classe Citta con nome, anno di fondazione e numero di abitanti ha campi simili a quelli di Persona, ma una città non è una persona. Avere campi in comune non basta a fare di una classe un sottoinsieme di un'altra: in Java è sottoinsieme solo ciò che viene dichiarato tale.
extends dichiara un sottoinsieme
public class Studente extends Persona {
public int matricola;
}
Studente è la sottoclasse (o classe derivata) e Persona la superclasse (classe base). In UML il rapporto si chiama generalizzazione, o relazione is-a.
Letteralmente extends significa «estende», ma in Java dichiara un sottoinsieme: Studente extends Persona si legge «gli studenti sono un sottoinsieme delle persone». Per decidere se usarlo conta una sola domanda: chi è sottoinsieme di chi. Non conta quali componenti la classe aggiunge: Citta extends Persona sarebbe sbagliato, perché Roma non è una persona.
Il sottoinsieme ha una conseguenza logica. Tutte le persone hanno nome e anno; ogni studente è una persona; quindi ogni studente ha nome e anno. Per questo Studente non ridichiara nome e anno: li eredita. I principi sono quattro:
- la sottoclasse eredita tutte le proprietà della superclasse, campi e associazioni;
- può avere proprietà ulteriori, come
matricola; - ogni oggetto della sottoclasse è anche un oggetto della superclasse: dove serve una persona si può usare uno studente;
- non vale il viceversa: una persona qualsiasi non è uno studente.
Sottoinsiemi e associazioni in UML
In un diagramma UML delle classi un rettangolo è un insieme. Due simboli vanno tenuti distinti:
- la linea è sempre un'associazione;
- la freccia con il triangolo vuoto è sempre un sottoinsieme.
Per esempio, gli studenti sono un sottoinsieme delle persone (freccia); persone e automobili sono invece legate da un'associazione «possiede» (linea). Il diagramma può descrivere più di quanto un programma usa: un'applicazione realizza soltanto la parte di schema che le serve.
Un'associazione è una relazione matematica, cioè un sottoinsieme del prodotto cartesiano dei due insiemi:
Con l'insieme delle persone e quello delle automobili, contiene le coppie (persona, automobile) in cui la persona possiede l'automobile.
Java non realizza le associazioni da sé: vanno scritte nel programma, con un riferimento o con una raccolta.
public class Automobile {
public String targa;
public int numeroTelaio;
public Persona proprietario;
}
public class Persona {
public String nome;
public int anno;
public HashSet<Automobile> possiede;
}
possiede è l'insieme delle automobili di una persona, e può essere vuoto. Le associazioni si ereditano come i campi: se una persona può possedere automobili, anche uno studente può farlo.
Altre sottoclassi si aggiungono allo stesso modo. Un Professore è una persona con in più il corso che tiene; nel diagramma è l'associazione «tiene» con la classe Corso, qui ridotta a una stringa.
public class Professore extends Persona {
public String corso;
}
Solo un Professore tiene un corso. Uno Studente può invece frequentare corsi, per esempio con un insieme di corsi. Una persona che non è né Studente né Professore non è legata ai corsi. Entrambi possono avere automobili, perché le ereditano da Persona.
Gli studenti lavoratori sono un sottoinsieme degli studenti, quindi anche delle persone. Aggiungono la ditta in cui lavorano. Nella gerarchia StudenteLavoratore estende Studente, che estende Persona come Professore.
Variabili, riferimenti e oggetti
Una variabile di classe contiene un riferimento a un oggetto. Una variabile di tipo Persona può riferirsi a uno Studente, perché ogni studente è una persona.
Studente s = new Studente();
s.nome = "Anna";
s.anno = 2004;
s.matricola = 1234567;
Automobile a = new Automobile();
a.proprietario = s; // ok: il proprietario è una Persona
HashSet<Persona> rubrica = new HashSet<Persona>();
rubrica.add(s); // ok: fra le persone ci sono anche gli studenti
HashSet<Studente> iscritti = new HashSet<Studente>();
Persona p = new Persona();
iscritti.add(p); // errore: p non è uno Studente
proprietario è dichiarato Persona e accetta uno studente. Una rubrica di persone contiene studenti e non studenti. Un insieme di studenti non accetta una persona qualsiasi.
Il tipo dichiarato della variabile e la classe dell'oggetto sono cose diverse. L'oggetto creato con new Persona() è una persona che non è uno studente; quello creato con new Studente() è uno studente e quindi anche una persona. La classe usata con new è la classe più specifica di cui l'oggetto è istanza.
In Java due classi o sono legate da extends, e una è sottoinsieme dell'altra, oppure sono disgiunte: nessun oggetto appartiene a entrambe. Studente e Professore sono disgiunti. Nella realtà una stessa persona può studiare e insegnare, ma Java non rappresenta insiemi che si sovrappongono solo in parte: è il limite del suo supporto ai sottoinsiemi.
Costruttori nelle sottoclassi
Senza costruttore i campi si assegnano uno alla volta, come sopra. Un costruttore ha il nome della classe, non ha tipo di ritorno (nemmeno void), inizializza i campi e si invoca con new. Se una classe non ne dichiara, Java fornisce un costruttore di default senza argomenti. Appena ne è dichiarato uno, il costruttore di default non c'è più.
public class Persona {
public String nome;
public int anno;
public HashSet<Automobile> possiede;
public Persona(String nome, int anno) {
this.nome = nome;
this.anno = anno;
possiede = new HashSet<Automobile>();
}
}
L'insieme delle automobili non è un argomento: nasce vuoto. Ora new Persona("Luca", 1980) funziona e new Persona() è un errore.
Anche Studente però smette di compilare. Creare uno studente significa inizializzare anche nome, anno e possiede, che sono ereditati, e lo fa un costruttore della superclasse. Per questo vale la regola:
- un costruttore della sottoclasse invoca un costruttore della superclasse con
super(...), come prima istruzione; - se non lo fa, Java inserisce
super()senza argomenti, ed è un errore quando la superclasse non ha un costruttore senza argomenti; - lo stesso vale per una sottoclasse senza costruttori: quello di default invoca
super().
public class Studente extends Persona {
public int matricola;
public Studente(String nome, int anno, int matricola) {
super(nome, anno);
this.matricola = matricola;
}
}
super(nome, anno) inizializza i campi della persona, senza ripetere il codice. Professore si scrive allo stesso modo, con corso al posto di matricola. Anche lo studente lavoratore passa a Studente i dati che non sono suoi:
public class StudenteLavoratore extends Studente {
public String ditta;
public StudenteLavoratore(String nome, int anno, int matricola, String ditta) {
super(nome, anno, matricola);
this.ditta = ditta;
}
}
I costruttori sono ripetitivi, e l'ambiente di sviluppo può generarli. Il risultato va controllato: per StudenteLavoratore Eclipse propone un costruttore con nome, anno e matricola, senza ditta, che va aggiunta a mano.
Una sottoclasse può non aggiungere campi: può avere solo metodi nuovi, o anche niente. Resta un sottoinsieme dichiarato, con un costruttore che chiama super(...) quando la superclasse lo richiede.
Formulario
Sottoclasse
- classe derivata
- superclasse = classe base
Sottoinsieme
extendssolo se c'è un sottoinsieme- senza
extends: classi disgiunte
Principi
- si ereditano le proprietà
- se ne possono aggiungere
- un oggetto della sottoclasse è anche della superclasse
- non vale il viceversa
UML
- linea: associazione
- freccia: sottoinsieme
Associazione
Costruttore
- nome della classe
- nessun tipo di ritorno
- si invoca con
new - senza costruttori, ne esiste uno senza argomenti
- dichiarato un costruttore, quello senza argomenti non c'è più
Costruttore della sottoclasse
super(...)per prima- senza
super(...), Java inseriscesuper() - errore se la superclasse non ha un costruttore senza argomenti