Vai al contenuto

Polimorfismo

In questa lezione impari a

  • comprendere il concetto di polimorfismo
  • usare riferimenti della superclasse per oggetti sottoclassi
  • capire il binding dinamico
  • imparare il casting di oggetti
  • usare instanceof per controllare i tipi

Cos'è il polimorfismo?

Il polimorfismo (dal greco "molte forme") permette di trattare oggetti di classi diverse attraverso un'interfaccia comune.

In pratica: un riferimento a una superclasse può puntare a oggetti di qualsiasi sottoclasse.

Esempio del mondo reale: Tutti i telecomandi hanno pulsanti play/stop, ma ogni telecomando controlla dispositivi diversi (TV, stereo, DVD). L'interfaccia è la stessa, ma il comportamento cambia!

Esempio base

// Superclasse
public class Animale {
    protected String nome;
    
    public Animale(String nome) {
        this.nome = nome;
    }
    
    public void emettiVerso() {
        System.out.println("L'animale emette un verso");
    }
    
    public void mangia() {
        System.out.println(nome + " sta mangiando");
    }
}

// Sottoclasse 1
public class Cane extends Animale {
    public Cane(String nome) {
        super(nome);
    }
    
    @Override
    public void emettiVerso() {
        System.out.println(nome + " dice: Bau bau! 🐕");
    }
    
    public void scodinzola() {
        System.out.println(nome + " scodinzola felice!");
    }
}

// Sottoclasse 2
public class Gatto extends Animale {
    public Gatto(String nome) {
        super(nome);
    }
    
    @Override
    public void emettiVerso() {
        System.out.println(nome + " dice: Miao! 🐱");
    }
    
    public void faiLeFusa() {
        System.out.println(nome + " fa le fusa... purr purr");
    }
}

Polimorfismo in azione!

public class TestPolimorfismo {
    public static void main(String[] args) {
        // POLIMORFISMO: riferimenti di tipo Animale che puntano a sottoclassi
        Animale animale1 = new Cane("Fido");
        Animale animale2 = new Gatto("Micio");
        Animale animale3 = new Cane("Rex");
        
        // Posso chiamare metodi della superclasse
        animale1.emettiVerso();  // Chiama la versione di Cane!
        animale2.emettiVerso();  // Chiama la versione di Gatto!
        animale3.emettiVerso();  // Chiama la versione di Cane!
        
        System.out.println();
        
        animale1.mangia();
        animale2.mangia();
        
        // animale1.scodinzola();  // ERRORE! Il riferimento è di tipo Animale
        // animale2.faiLeFusa();   // ERRORE! Il riferimento è di tipo Animale
    }
}
Fido dice: Bau bau! 🐕
Micio dice: Miao! 🐱
Rex dice: Bau bau! 🐕

Fido sta mangiando
Micio sta mangiando

Magia del polimorfismo. Anche se i riferimenti sono di tipo Animale, quando chiamiamo emettiVerso(), Java chiama la versione corretta in base all'oggetto REALE (Cane o Gatto)!

Questo si chiama binding dinamico o late binding.

Array polimorfici

Il polimorfismo diventa potentissimo con array e collezioni:

public class TestArrayPolimorfici {
    public static void main(String[] args) {
        // Array di Animale che contiene oggetti diversi!
        Animale[] zoo = new Animale[5];
        
        zoo[0] = new Cane("Fido");
        zoo[1] = new Gatto("Micio");
        zoo[2] = new Cane("Rex");
        zoo[3] = new Gatto("Felix");
        zoo[4] = new Cane("Pluto");
        
        System.out.println("=== Tutti gli animali emettono il loro verso ===");
        for (Animale animale : zoo) {
            animale.emettiVerso();  // Chiama il metodo corretto per ogni tipo!
        }
        
        System.out.println("\n=== Tutti gli animali mangiano ===");
        for (Animale animale : zoo) {
            animale.mangia();
        }
    }
}
=== Tutti gli animali emettono il loro verso ===
Fido dice: Bau bau! 🐕
Micio dice: Miao! 🐱
Rex dice: Bau bau! 🐕
Felix dice: Miao! 🐱
Pluto dice: Bau bau! 🐕

=== Tutti gli animali mangiano ===
Fido sta mangiando
Micio sta mangiando
Rex sta mangiando
Felix sta mangiando
Pluto sta mangiando

instanceof - controllare il tipo

L'operatore instanceof verifica se un oggetto è un'istanza di una certa classe.

Sintassi: oggetto instanceof NomeClasse

Restituisce true o false

public class TestInstanceOf {
    public static void main(String[] args) {
        Animale[] animali = {
            new Cane("Fido"),
            new Gatto("Micio"),
            new Cane("Rex")
        };
        
        for (Animale animale : animali) {
            animale.emettiVerso();
            
            // Controllo il tipo e chiamo metodi specifici
            if (animale instanceof Cane) {
                Cane cane = (Cane) animale;  // Cast
                cane.scodinzola();
            } else if (animale instanceof Gatto) {
                Gatto gatto = (Gatto) animale;  // Cast
                gatto.faiLeFusa();
            }
            
            System.out.println();
        }
    }
}
Fido dice: Bau bau! 🐕
Fido scodinzola felice!

Micio dice: Miao! 🐱
Micio fa le fusa... purr purr

Rex dice: Bau bau! 🐕
Rex scodinzola felice!

Casting di oggetti

Due tipi di casting. Upcasting (automatico): da sottoclasse a superclasse

Cane cane = new Cane("Fido");
Animale animale = cane;  // Automatico!

Downcasting (esplicito): da superclasse a sottoclasse

Animale animale = new Cane("Fido");
Cane cane = (Cane) animale;  // Serve il cast esplicito!

Attenzione al downcasting. Il downcasting può causare un ClassCastException se sbagliamo tipo:

Animale animale = new Gatto("Micio");
Cane cane = (Cane) animale;  // ERRORE A RUNTIME!

Usa sempre instanceof prima del cast!

Esempio pratico: sistema di pagamento

// Superclasse astratta
public abstract class MetodoPagamento {
    protected double importo;
    
    public MetodoPagamento(double importo) {
        this.importo = importo;
    }
    
    // Metodo astratto - ogni sottoclasse lo implementa
    public abstract void effettuaPagamento();
    
    public void mostraRiepilogo() {
        System.out.println("Importo da pagare: €" + importo);
    }
}

// Sottoclasse 1
public class CartaCredito extends MetodoPagamento {
    private String numeroCartanumero;
    private String intestatario;
    
    public CartaCredito(double importo, String numeroCarta, String intestatario) {
        super(importo);
        this.numeroCarta = numeroCarta;
        this.intestatario = intestatario;
    }
    
    @Override
    public void effettuaPagamento() {
        System.out.println("Pagamento con Carta di Credito");
        System.out.println("Intestatario: " + intestatario);
        System.out.println("Carta: **** **** **** " + numeroCarta.substring(12));
        System.out.println("Importo: €" + importo);
        System.out.println("✓ Pagamento autorizzato!");
    }
}

// Sottoclasse 2
public class PayPal extends MetodoPagamento {
    private String email;
    
    public PayPal(double importo, String email) {
        super(importo);
        this.email = email;
    }
    
    @Override
    public void effettuaPagamento() {
        System.out.println("Pagamento con PayPal");
        System.out.println("Account: " + email);
        System.out.println("Importo: €" + importo);
        System.out.println("✓ Pagamento completato!");
    }
}

// Sottoclasse 3
public class Contanti extends MetodoPagamento {
    private double importoRicevuto;
    
    public Contanti(double importo, double importoRicevuto) {
        super(importo);
        this.importoRicevuto = importoRicevuto;
    }
    
    @Override
    public void effettuaPagamento() {
        System.out.println("Pagamento in Contanti");
        System.out.println("Importo dovuto: €" + importo);
        System.out.println("Importo ricevuto: €" + importoRicevuto);
        double resto = importoRicevuto - importo;
        if (resto > 0) {
            System.out.println("Resto: €" + resto);
        }
        System.out.println("✓ Pagamento ricevuto!");
    }
}
public class TestPagamenti {
    public static void main(String[] args) {
        // Array polimorfico di metodi di pagamento
        MetodoPagamento[] pagamenti = new MetodoPagamento[3];
        
        pagamenti[0] = new CartaCredito(150.00, "1234567812345678", "Mario Rossi");
        pagamenti[1] = new PayPal(89.99, "mario.rossi@email.com");
        pagamenti[2] = new Contanti(25.50, 30.00);
        
        // Elaboro tutti i pagamenti allo stesso modo!
        for (int i = 0; i < pagamenti.length; i++) {
            System.out.println("=== Pagamento " + (i+1) + " ===");
            pagamenti[i].mostraRiepilogo();
            pagamenti[i].effettuaPagamento();
            System.out.println();
        }
        
        // Calcolo totale incassato
        double totale = 0;
        for (MetodoPagamento p : pagamenti) {
            totale += p.importo;
        }
        System.out.println("Totale incassato: €" + totale);
    }
}
=== Pagamento 1 ===
Importo da pagare: €150.0
Pagamento con Carta di Credito
Intestatario: Mario Rossi
Carta: **** **** **** 5678
Importo: €150.0
✓ Pagamento autorizzato!

=== Pagamento 2 ===
Importo da pagare: €89.99
Pagamento con PayPal
Account: mario.rossi@email.com
Importo: €89.99
✓ Pagamento completato!

=== Pagamento 3 ===
Importo da pagare: €25.5
Pagamento in Contanti
Importo dovuto: €25.5
Importo ricevuto: €30.0
Resto: €4.5
✓ Pagamento ricevuto!

Totale incassato: €265.49

Vantaggi del polimorfismo

Perché usare il polimorfismo?.

  • Codice più flessibile: posso aggiungere nuove sottoclassi senza modificare il codice esistente
  • Codice più pulito: evito lunghe catene di if-else
  • Interfaccia uniforme: tratto oggetti diversi allo stesso modo
  • Estensibilità: facile aggiungere nuove funzionalità
  • Manutenibilità: modifiche localizzate nelle singole classi

Metodi polimorfici

public class GestioneAnimali {
    // Metodo polimorfico - accetta qualsiasi Animale
    public static void faiEmettereVerso(Animale animale) {
        System.out.println("Facciamo emettere il verso a " + animale.nome);
        animale.emettiVerso();
    }
    
    // Metodo che lavora con array di Animale
    public static void curaAnimali(Animale[] animali) {
        System.out.println("=== Cura degli animali ===");
        for (Animale animale : animali) {
            System.out.println("Curando " + animale.nome + "...");
            animale.mangia();
            animale.emettiVerso();
            System.out.println();
        }
    }
    
    public static void main(String[] args) {
        Cane cane = new Cane("Fido");
        Gatto gatto = new Gatto("Micio");
        
        // Posso passare sia Cane che Gatto!
        faiEmettereVerso(cane);
        faiEmettereVerso(gatto);
        
        System.out.println();
        
        Animale[] animali = {
            new Cane("Rex"),
            new Gatto("Felix"),
            new Cane("Pluto")
        };
        
        curaAnimali(animali);
    }
}

Esercizi guidati

⭐⭐ Medio

Esercizio 1 – forme geometriche polimorfiche.

Obiettivo: Usare il polimorfismo con forme geometriche.

Requisiti:

  1. Crea classe astratta Forma con:
    • Metodo astratto: calcolaArea()
    • Metodo astratto: calcolaPerimetro()
  2. Crea sottoclassi: Cerchio, Rettangolo, Triangolo
  3. Nel main, crea array di Forma con oggetti diversi
  4. Calcola e stampa area totale di tutte le forme

⭐⭐ Medio

Esercizio 2 – strumenti musicali.

Obiettivo: Simulare un'orchestra con polimorfismo.

Requisiti:

  • Classe astratta StrumentoMusicale con metodo suona()
  • Sottoclassi: Chitarra, Pianoforte, Batteria
  • Ogni strumento suona() in modo diverso
  • Crea metodo concerti(StrumentoMusicale[] orchestra)

Esercizi da svolgere

⭐ Facile

Esercizio 3 – veicoli in movimento.

Crea Veicolo → Auto/Moto/Bici con:

  • Metodo astratto muovi()
  • Array di veicoli
  • Fai muovere tutti i veicoli

⭐⭐ Medio

Esercizio 4 – sistema notifiche.

Crea Notifica → Email/SMS/Push con:

  • Metodo invia()
  • Usa polimorfismo per inviare diverse notifiche
  • Conta quante notifiche di ogni tipo

⭐⭐⭐ Difficile

Esercizio 5 – dipendenti e stipendi.

Crea Dipendente → Manager/Impiegato/Stagista con:

  • Metodo calcolaStipendio() diverso per ciascuno
  • Array di dipendenti misti
  • Calcola stipendio totale aziendale
  • Trova dipendente con stipendio massimo

Concetti chiave da ricordare

  • Polimorfismo = molte forme, un'interfaccia
  • Riferimento superclasse → oggetto sottoclasse
  • Binding dinamico: metodo scelto a runtime
  • instanceof verifica il tipo
  • Upcasting automatico, downcasting esplicito
  • Array polimorfici contengono oggetti di tipi diversi
  • Rende il codice flessibile ed estensibile

Quando usare il polimorfismo

Situazioni ideali.

  • Collezioni di oggetti di tipi diversi ma correlati
  • Quando vuoi trattare oggetti diversi in modo uniforme
  • Per rendere il codice estensibile (nuove classi senza modificare esistente)
  • Nei pattern di design (Strategy, Factory, etc.)