Gerarchie di classi in Java
- Le gerarchie di classi in Java permettono di organizzare le classi in strutture ad albero, facilitando il riutilizzo del codice e la gestione delle relazioni tra classi.
- Le classi possono estendere altre classi, ereditando i loro attributi e metodi, e possono anche implementare interfacce per definire contratti di comportamento.
- Le gerarchie multilivello consentono di creare strutture complesse, dove le sottoclassi possono ereditare da più livelli di superclassi, facilitando l'organizzazione e la riusabilità del codice.
Creare una Gerarchia Multilivello
Fino a questo punto, abbiamo utilizzato semplici gerarchie di classi che consistono solo di una superclasse e di una sottoclasse.
Tuttavia, è possibile costruire gerarchie che contengano tanti livelli di ereditarietà quanto si desidera. Come accennato, è perfettamente accettabile usare una sottoclasse come superclasse di un'altra. Per esempio, date tre classi chiamate A
, B
e C
, C
può essere una sottoclasse di B
, che è una sottoclasse di A
.
Quando si verifica questo tipo di situazione, ogni sottoclasse eredita tutti i tratti trovati in tutte le sue superclassi. In questo caso, C
eredita tutti gli aspetti di B
e A
.
Per capire come una gerarchia multilivello può essere utile, consideriamo il seguente programma. In esso, la sottoclasse PesoScatola
è usata come superclasse per creare la sottoclasse chiamata Spedizione
. Spedizione
eredita tutti i tratti di PesoScatola
e Scatola
, e aggiunge un campo chiamato costo
, che contiene il costo di spedizione di tale pacco.
// Estende PesoScatola per includere i costi di spedizione.
// Inizia con Scatola.
class Scatola {
private double larghezza;
private double altezza;
private double profondita;
// costruisce clone di un oggetto
Scatola(Scatola ogg) { // passare oggetto al costruttore
larghezza = ogg.larghezza;
altezza = ogg.altezza;
profondita = ogg.profondita;
}
// costruttore usato quando tutte le dimensioni sono specificate
Scatola(double l, double a, double p) {
larghezza = l;
altezza = a;
profondita = p;
}
// costruttore usato quando nessuna dimensione è specificata
Scatola() {
larghezza = -1; // usare -1 per indicare
altezza = -1; // un oggetto non inizializzato
profondita = -1; // scatola
}
// costruttore usato quando viene creato un cubo
Scatola(double lato) {
larghezza = altezza = profondita = lato;
}
// calcolare e restituire il volume
double volume() {
return larghezza * altezza * profondita;
}
}
// Aggiunge il peso.
class PesoScatola extends Scatola {
double peso; // peso della scatola
// costruisce clone di un oggetto
PesoScatola(PesoScatola ogg) { // passare oggetto al costruttore
super(ogg);
peso = ogg.peso;
}
// costruttore quando tutti i parametri sono specificati
PesoScatola(double l, double a, double p, double m) {
super(l, a, p); // chiamare costruttore della superclasse
peso = m;
}
// costruttore predefinito
PesoScatola() {
super();
peso = -1;
}
// costruttore usato quando viene creato un cubo
PesoScatola(double lato, double m) {
super(lato);
peso = m;
}
}
// Aggiunge i costi di spedizione.
class Spedizione extends PesoScatola {
double costo;
// costruisce clone di un oggetto
Spedizione(Spedizione ogg) { // passare oggetto al costruttore
super(ogg);
costo = ogg.costo;
}
// costruttore quando tutti i parametri sono specificati
Spedizione(double l, double a, double p,
double m, double c) {
super(l, a, p, m); // chiamare costruttore della superclasse
costo = c;
}
// costruttore predefinito
Spedizione() {
super();
costo = -1;
}
// costruttore usato quando viene creato un cubo
Spedizione(double lato, double m, double c) {
super(lato, m);
costo = c;
}
}
class DemoSpedizione {
public static void main(String[] args) {
Spedizione spedizione1 =
new Spedizione(10, 20, 15, 10, 3.41);
Spedizione spedizione2 =
new Spedizione(2, 3, 4, 0.76, 1.28);
double vol;
vol = spedizione1.volume();
System.out.println("Volume della spedizione1 è " + vol);
System.out.println("Peso della spedizione1 è "
+ spedizione1.peso);
System.out.println("Costo di spedizione: $" + spedizione1.costo);
System.out.println();
vol = spedizione2.volume();
System.out.println("Volume della spedizione2 è " + vol);
System.out.println("Peso della spedizione2 è "
+ spedizione2.peso);
System.out.println("Costo di spedizione: $" + spedizione2.costo);
}
}
L'output di questo programma è mostrato qui:
Volume della spedizione1 è 3000.0
Peso della spedizione1 è 10.0
Costo di spedizione: $3.41
Volume della spedizione2 è 24.0
Peso della spedizione2 è 0.76
Costo di spedizione: $1.28
Grazie all'ereditarietà, Spedizione
può fare uso delle classi precedentemente definite Scatola
e PesoScatola
, aggiungendo solo le informazioni extra di cui ha bisogno per la propria applicazione specifica. Questo è parte del valore dell'ereditarietà: consente il riutilizzo del codice.
Questo esempio illustra anche un altro punto importante: super()
si riferisce sempre al costruttore nella superclasse più vicina.
Il super()
in Spedizione
richiama il costruttore in PesoScatola
. Il super()
in PesoScatola
richiama il costruttore in Scatola
. In una gerarchia di classi, se un costruttore di una superclasse richiede argomenti, allora tutte le sottoclassi devono passare tali argomenti verso l'alto. Questo è vero sia che una sottoclasse necessiti o meno di argomenti propri.
Organizzazione del codice nelle gerarchie
Nel programma precedente, l'intera gerarchia di classi, compresa Scatola
, PesoScatola
e Spedizione
, è mostrata tutta in un unico file. Questo è solo per comodità. In Java, tutte e tre le classi avrebbero potuto essere collocate in file propri e compilate separatamente. Infatti, usare file separati è la norma, non l'eccezione, nella creazione di gerarchie di classi.