Gerarchie di classi in Java

Concetti Chiave
  • 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.

Consiglio

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.