☕ Lezione 7

Ereditarietà - Riusare e Estendere il Codice

Ereditarietà: Il Pilastro della Riusabilità

🎯 Obiettivi della lezione

💡 Cos'è l'Ereditarietà?

L'ereditarietà permette di creare una nuova classe basata su una classe esistente.

La nuova classe (chiamata sottoclasse o classe figlia) eredita attributi e metodi dalla classe esistente (chiamata superclasse o classe padre).

È come dire: "X è un tipo di Y" (es: un Cane è un tipo di Animale)

🌳 Gerarchia di Classi

Immagina questa situazione:

Scenario: Sistema di gestione veicoli

Vogliamo gestire diversi tipi di veicoli:

  • Tutti i veicoli hanno: marca, modello, anno
  • Le Auto hanno in più: numero porte
  • Le Moto hanno in più: tipo di motore
  • I Camion hanno in più: capacità di carico

Invece di ripetere marca, modello, anno in ogni classe, usiamo l'ereditarietà!

📝 Esempio Base: Veicolo e Auto

// SUPERCLASSE (classe padre)
public class Veicolo {
    // Attributi comuni a tutti i veicoli
    protected String marca;
    protected String modello;
    protected int anno;
    
    // Costruttore
    public Veicolo(String marca, String modello, int anno) {
        this.marca = marca;
        this.modello = modello;
        this.anno = anno;
    }
    
    // Metodi comuni
    public void mostraInfo() {
        System.out.println("Marca: " + marca);
        System.out.println("Modello: " + modello);
        System.out.println("Anno: " + anno);
    }
    
    public void avvia() {
        System.out.println("Il veicolo è stato avviato.");
    }
}

// SOTTOCLASSE (classe figlia)
public class Auto extends Veicolo {
    // Attributo specifico per Auto
    private int numeroPorte;
    
    // Costruttore
    public Auto(String marca, String modello, int anno, int numeroPorte) {
        super(marca, modello, anno);  // Chiama costruttore della superclasse
        this.numeroPorte = numeroPorte;
    }
    
    // Metodo specifico per Auto
    public void apriPorta() {
        System.out.println("Porta aperta!");
    }
    
    // Override del metodo della superclasse
    @Override
    public void mostraInfo() {
        super.mostraInfo();  // Chiama metodo del padre
        System.out.println("Numero porte: " + numeroPorte);
    }
}

🧪 Usare l'Ereditarietà

public class TestVeicoli {
    public static void main(String[] args) {
        // Creare un'Auto
        Auto miaAuto = new Auto("Fiat", "500", 2020, 3);
        
        System.out.println("=== Informazioni Auto ===");
        miaAuto.mostraInfo();  // Metodo override
        miaAuto.avvia();       // Metodo ereditato
        miaAuto.apriPorta();   // Metodo specifico di Auto
        
        System.out.println();
        
        // Creare un Veicolo generico
        Veicolo veicoloGenerico = new Veicolo("Honda", "CBR", 2021);
        System.out.println("=== Informazioni Veicolo ===");
        veicoloGenerico.mostraInfo();
        veicoloGenerico.avvia();
        // veicoloGenerico.apriPorta();  // ERRORE! Non esiste in Veicolo
    }
}
=== Informazioni Auto === Marca: Fiat Modello: 500 Anno: 2020 Numero porte: 3 Il veicolo è stato avviato. Porta aperta! === Informazioni Veicolo === Marca: Honda Modello: CBR Anno: 2021 Il veicolo è stato avviato.

🔑 Parole Chiave Importanti

extends

class Auto extends Veicolo

Indica che Auto eredita da Veicolo. Auto "è un tipo di" Veicolo.

super

super(parametri) - chiama il costruttore della superclasse

super.metodo() - chiama un metodo della superclasse

IMPORTANTE: super() deve essere la PRIMA istruzione nel costruttore!

protected

Un modificatore di accesso che permette l'accesso:

  • Dalla classe stessa
  • Dalle sottoclassi
  • Da altre classi nello stesso package

È un "via di mezzo" tra private e public, usato spesso con l'ereditarietà.

🏗️ Gerarchia Completa

// SUPERCLASSE
public class Veicolo {
    protected String marca;
    protected String modello;
    protected int anno;
    protected double velocitaMassima;
    
    public Veicolo(String marca, String modello, int anno, double velocitaMassima) {
        this.marca = marca;
        this.modello = modello;
        this.anno = anno;
        this.velocitaMassima = velocitaMassima;
    }
    
    public void mostraInfo() {
        System.out.println("=== Informazioni Veicolo ===");
        System.out.println("Marca: " + marca);
        System.out.println("Modello: " + modello);
        System.out.println("Anno: " + anno);
        System.out.println("Velocità max: " + velocitaMassima + " km/h");
    }
    
    public void avvia() {
        System.out.println(marca + " " + modello + " avviato!");
    }
}

// SOTTOCLASSE 1: Auto
public class Auto extends Veicolo {
    private int numeroPorte;
    private String tipologia;  // berlina, suv, city car, etc.
    
    public Auto(String marca, String modello, int anno, double velocitaMassima,
                int numeroPorte, String tipologia) {
        super(marca, modello, anno, velocitaMassima);
        this.numeroPorte = numeroPorte;
        this.tipologia = tipologia;
    }
    
    @Override
    public void mostraInfo() {
        super.mostraInfo();
        System.out.println("Numero porte: " + numeroPorte);
        System.out.println("Tipologia: " + tipologia);
    }
    
    public void apriPortabagagli() {
        System.out.println("Portabagagli aperto!");
    }
}

// SOTTOCLASSE 2: Moto
public class Moto extends Veicolo {
    private String tipoMotore;
    private boolean hasBauletto;
    
    public Moto(String marca, String modello, int anno, double velocitaMassima,
                String tipoMotore, boolean hasBauletto) {
        super(marca, modello, anno, velocitaMassima);
        this.tipoMotore = tipoMotore;
        this.hasBauletto = hasBauletto;
    }
    
    @Override
    public void mostraInfo() {
        super.mostraInfo();
        System.out.println("Tipo motore: " + tipoMotore);
        System.out.println("Bauletto: " + (hasBauletto ? "Sì" : "No"));
    }
    
    public void impennaRuota() {
        System.out.println("Impennata! 🏍️");
    }
}

// SOTTOCLASSE 3: Camion
public class Camion extends Veicolo {
    private double capacitaCaricoTon;
    private int numeroAssi;
    
    public Camion(String marca, String modello, int anno, double velocitaMassima,
                  double capacitaCaricoTon, int numeroAssi) {
        super(marca, modello, anno, velocitaMassima);
        this.capacitaCaricoTon = capacitaCaricoTon;
        this.numeroAssi = numeroAssi;
    }
    
    @Override
    public void mostraInfo() {
        super.mostraInfo();
        System.out.println("Capacità carico: " + capacitaCaricoTon + " tonnellate");
        System.out.println("Numero assi: " + numeroAssi);
    }
    
    public void carica(double tonnellate) {
        if (tonnellate <= capacitaCaricoTon) {
            System.out.println("Caricate " + tonnellate + " tonnellate.");
        } else {
            System.out.println("Carico troppo pesante!");
        }
    }
}

🧪 Test Completo

public class TestGerarchiaVeicoli {
    public static void main(String[] args) {
        // Creare diversi veicoli
        Auto auto = new Auto("Fiat", "Panda", 2022, 160, 5, "City Car");
        Moto moto = new Moto("Ducati", "Monster", 2023, 240, "4 cilindri", true);
        Camion camion = new Camion("Iveco", "Daily", 2021, 120, 3.5, 2);
        
        // Auto
        auto.mostraInfo();
        auto.avvia();
        auto.apriPortabagagli();
        System.out.println();
        
        // Moto
        moto.mostraInfo();
        moto.avvia();
        moto.impennaRuota();
        System.out.println();
        
        // Camion
        camion.mostraInfo();
        camion.avvia();
        camion.carica(3.0);
        camion.carica(5.0);
    }
}
=== Informazioni Veicolo === Marca: Fiat Modello: Panda Anno: 2022 Velocità max: 160.0 km/h Numero porte: 5 Tipologia: City Car Fiat Panda avviato! Portabagagli aperto! === Informazioni Veicolo === Marca: Ducati Modello: Monster Anno: 2023 Velocità max: 240.0 km/h Tipo motore: 4 cilindri Bauletto: Sì Ducati Monster avviato! Impennata! 🏍️ === Informazioni Veicolo === Marca: Iveco Modello: Daily Anno: 2021 Velocità max: 120.0 km/h Capacità carico: 3.5 tonnellate Numero assi: 2 Iveco Daily avviato! Caricate 3.0 tonnellate. Carico troppo pesante!

📖 Esempio: Persone e Studenti

// Superclasse
public class Persona {
    protected String nome;
    protected String cognome;
    protected int eta;
    
    public Persona(String nome, String cognome, int eta) {
        this.nome = nome;
        this.cognome = cognome;
        this.eta = eta;
    }
    
    public void presenta() {
        System.out.println("Ciao, sono " + nome + " " + cognome);
    }
    
    public String getNomeCompleto() {
        return nome + " " + cognome;
    }
    
    public int getEta() {
        return eta;
    }
}

// Sottoclasse
public class Studente extends Persona {
    private String matricola;
    private String corso;
    private double mediaVoti;
    
    public Studente(String nome, String cognome, int eta, 
                    String matricola, String corso) {
        super(nome, cognome, eta);
        this.matricola = matricola;
        this.corso = corso;
        this.mediaVoti = 0.0;
    }
    
    public void aggiungiVoto(double voto) {
        // Semplificato: in realtà dovresti calcolare la media vera
        mediaVoti = (mediaVoti + voto) / 2;
    }
    
    @Override
    public void presenta() {
        super.presenta();  // Chiama presenta() di Persona
        System.out.println("Sono uno studente di " + corso);
        System.out.println("Matricola: " + matricola);
    }
    
    public boolean ePromosso() {
        return mediaVoti >= 6.0;
    }
    
    public void mostraInfo() {
        System.out.println("Studente: " + getNomeCompleto());
        System.out.println("Età: " + getEta());
        System.out.println("Corso: " + corso);
        System.out.println("Media: " + mediaVoti);
    }
}

// Altra sottoclasse
public class Docente extends Persona {
    private String materia;
    private int anniServizio;
    
    public Docente(String nome, String cognome, int eta, 
                   String materia, int anniServizio) {
        super(nome, cognome, eta);
        this.materia = materia;
        this.anniServizio = anniServizio;
    }
    
    @Override
    public void presenta() {
        super.presenta();
        System.out.println("Insegno " + materia);
        System.out.println("Ho " + anniServizio + " anni di servizio");
    }
    
    public void faiLezione() {
        System.out.println("Il prof. " + cognome + " sta facendo lezione di " + materia);
    }
}
public class TestScuola {
    public static void main(String[] args) {
        Studente studente = new Studente("Mario", "Rossi", 17, "12345", "Informatica");
        Docente docente = new Docente("Laura", "Bianchi", 45, "Matematica", 20);
        
        studente.presenta();
        studente.aggiungiVoto(8);
        studente.aggiungiVoto(7);
        studente.mostraInfo();
        System.out.println("Promosso: " + studente.ePromosso());
        
        System.out.println("\n---\n");
        
        docente.presenta();
        docente.faiLezione();
    }
}
Ciao, sono Mario Rossi Sono uno studente di Informatica Matricola: 12345 Studente: Mario Rossi Età: 17 Corso: Informatica Media: 3.75 Promosso: false --- Ciao, sono Laura Bianchi Insegno Matematica Ho 20 anni di servizio Il prof. Bianchi sta facendo lezione di Matematica

⚠️ Regole dell'Ereditarietà

📋 Regole Importanti:

  • In Java una classe può ereditare da UNA SOLA classe (no ereditarietà multipla)
  • Una classe può avere MOLTE sottoclassi
  • L'ereditarietà è transitiva: se C eredita da B e B da A, allora C eredita anche da A
  • I costruttori NON vengono ereditati
  • I metodi private NON vengono ereditati
  • Tutti gli oggetti ereditano da Object (la classe radice di Java)

🎨 Override dei Metodi

L'override permette a una sottoclasse di fornire una propria implementazione di un metodo ereditato.

Usa l'annotazione @Override per chiarezza (opzionale ma consigliato).

public class Animale {
    public void emettiVerso() {
        System.out.println("L'animale emette un verso");
    }
}

public class Cane extends Animale {
    @Override
    public void emettiVerso() {
        System.out.println("Bau bau! 🐕");
    }
}

public class Gatto extends Animale {
    @Override
    public void emettiVerso() {
        System.out.println("Miao! 🐱");
    }
}

public class Mucca extends Animale {
    @Override
    public void emettiVerso() {
        System.out.println("Muuu! 🐄");
    }
}

// Test
Animale a1 = new Cane();
Animale a2 = new Gatto();
Animale a3 = new Mucca();

a1.emettiVerso();  // Bau bau! 🐕
a2.emettiVerso();  // Miao! 🐱
a3.emettiVerso();  // Muuu! 🐄

✏️ Esercizi Guidati

Esercizio 1: Forme Geometriche

MEDIO

Obiettivo: Creare una gerarchia di forme geometriche.

Requisiti:

  1. Crea una superclasse Forma con:
    • Attributo protected: colore
    • Metodo: mostraInfo()
  2. Crea sottoclasse Rettangolo con:
    • Attributi: base, altezza
    • Metodo: calcolaArea()
    • Override mostraInfo()
  3. Crea sottoclasse Cerchio con:
    • Attributo: raggio
    • Metodo: calcolaArea()
    • Override mostraInfo()

Esercizio 2: Dipendenti Azienda

MEDIO

Obiettivo: Gestire diversi tipi di dipendenti.

Requisiti:

  • Superclasse Dipendente: nome, cognome, stipendioBase
  • Sottoclasse Manager: aggiunge bonus
  • Sottoclasse Operaio: aggiunge oreStraordinario
  • Metodo calcolaStipendio() diverso per ogni tipo

💪 Esercizi da Svolgere

Esercizio 3: Animali Domestici

FACILE

Crea gerarchia Animale → Cane/Gatto con:

  • Attributi comuni: nome, età
  • Metodo emettiVerso() diverso per ogni animale
  • Metodi specifici: abbaia(), miagola()

Esercizio 4: Conti Bancari

MEDIO

Crea ContoBancario → ContoCorrente/ContoRisparmio:

  • ContoCorrente: con fido
  • ContoRisparmio: con tasso interesse
  • Metodi preleva() diversi

Esercizio 5: Sistema Biblioteca

DIFFICILE

Crea ElementoBiblioteca → Libro/Rivista/DVD:

  • Attributi comuni: titolo, anno
  • Libro: autore, pagine
  • Rivista: numero, mese
  • DVD: durata, regista
  • Metodo mostraDettagli() per tutti

🎯 Concetti Chiave da Ricordare

  • extends crea una relazione "è un tipo di"
  • super() chiama il costruttore del padre (prima istruzione!)
  • super.metodo() chiama metodo del padre
  • protected = accessibile nelle sottoclassi
  • @Override = ridefinisci metodo ereditato
  • Solo ereditarietà singola in Java
  • Riusa il codice ed evita duplicazioni!