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.