Polimorfismo: Il Potere della Flessibilità
🎯 Obiettivi della lezione
- 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
}
}
✨ 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();
}
}
}
🔍 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();
}
}
}
🔄 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);
}
}
🎯 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
Esercizio 1: Forme Geometriche Polimorfiche
MEDIOObiettivo: Usare il polimorfismo con forme geometriche.
Requisiti:
- Crea classe astratta Forma con:
- Metodo astratto: calcolaArea()
- Metodo astratto: calcolaPerimetro()
- Crea sottoclassi: Cerchio, Rettangolo, Triangolo
- Nel main, crea array di Forma con oggetti diversi
- Calcola e stampa area totale di tutte le forme
Esercizio 2: Strumenti Musicali
MEDIOObiettivo: 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
Esercizio 3: Veicoli in Movimento
FACILECrea Veicolo → Auto/Moto/Bici con:
- Metodo astratto muovi()
- Array di veicoli
- Fai muovere tutti i veicoli
Esercizio 4: Sistema Notifiche
MEDIOCrea Notifica → Email/SMS/Push con:
- Metodo invia()
- Usa polimorfismo per inviare diverse notifiche
- Conta quante notifiche di ogni tipo
Esercizio 5: Dipendenti e Stipendi
DIFFICILECrea 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.)