Menu

Polimorfismo in Java: un'interfaccia, tante forme

Come il polimorfismo in Java permette a una variabile di riferirsi a tanti tipi, perché i metodi sovrascritti vengono scelti a runtime e come usare upcasting, downcasting e instanceof in sicurezza.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

Un riferimento, tanti tipi

Il polimorfismo è il frutto dell'ereditarietà e delle interfacce. Significa che una sola variabile con il tipo di un padre (o di un'interfaccia) può contenere un oggetto di qualsiasi sottotipo, e quando chiami un suo metodo Java esegue la versione della classe reale dell'oggetto, non quella suggerita dal tipo dichiarato della variabile.

Hai già visto la base nella pagina sull'ereditarietà: una sottoclasse sovrascrive un metodo del padre. Il polimorfismo è ciò che rende utile quell'override: ti permette di scrivere codice per il tipo generale e di lasciare che ogni oggetto concreto si comporti a modo suo.

Sia a sia b sono dichiarati come Animal, eppure ciascuno stampa il proprio verso. La scelta avviene a runtime in base all'oggetto reale.

Dispatch dinamico dei metodi

Il meccanismo dietro a tutto questo è il dispatch dinamico dei metodi: per un metodo di istanza sovrascritto, la JVM guarda la classe dell'oggetto a runtime per decidere quale implementazione chiamare. Il compilatore controlla solo che il metodo esista nel tipo dichiarato; la scelta vera e propria viene rimandata a quando il programma è in esecuzione.

È questo che permette a un solo ciclo di gestire un insieme misto di tipi senza mai chiedere che cosa sia ciascuno:

Il ciclo conosce solo Shape. Se più avanti aggiungi un Triangle extends Shape, questo codice continua a funzionare senza modifiche: è proprio questo il punto. Il codice dipende dall'astrazione, non dall'elenco concreto dei tipi.

Upcasting e downcasting

Salvare un Dog in una variabile Animal è un upcasting: si sale nella gerarchia verso un tipo più generale. È sempre sicuro e Java lo fa in modo implicito, perché ogni Dog è un Animal.

Andare nella direzione opposta è un downcasting: prendi un riferimento al padre e lo tratti come un sottotipo specifico. È una forma di type casting tra tipi riferimento, ed è valida solo se l'oggetto è davvero di quel sottotipo, quindi devi scrivere il cast in modo esplicito e rischi una ClassCastException se sbagli:

L'ultimo cast compila senza problemi, perché il compilatore non può dimostrare che è sbagliato, ma esplode quando viene eseguito, perché un Cat non è un Dog. Non fare mai un downcast sulla fiducia.

Proteggere i downcast con instanceof

Prima di un downcast, controlla il tipo reale con instanceof. Il Java moderno ti permette di legare il risultato nella stessa espressione (pattern matching per instanceof), così eviti il cast separato:

instanceof restituisce false per null, quindi il controllo ti protegge anche da una NullPointerException. Detto questo, se ti ritrovi a scrivere lunghe catene di instanceof, spesso è segno che quel comportamento appartiene alle classi stesse come metodo sovrascritto: lascia che sia il polimorfismo a scegliere il ramo al posto tuo.

Overriding e overloading

Questi due termini si somigliano ma non hanno niente in comune, e confonderli è una fonte classica di dubbi.

L'overriding è una sottoclasse che sostituisce un metodo del padre con la stessa identica firma. Si risolve a runtime in base al tipo dell'oggetto: è il polimorfismo che abbiamo usato finora.

L'overloading è una classe che ha più metodi con lo stesso nome ma liste di parametri diverse. Si risolve in fase di compilazione in base ai tipi degli argomenti, senza alcun dispatch a runtime:

Il compilatore sceglie il describe corrispondente basandosi solo sul tipo statico dell'argomento. Non ci sono oggetti padre e figlio in gioco, quindi questo non è polimorfismo a runtime: è solo il riutilizzo di un nome di metodo.

Una trappola comune: i campi non sono polimorfici

Solo i metodi di istanza vengono scelti a runtime. I campi e i metodi statici si risolvono in base al tipo dichiarato, e questo confonde molte persone:

p.name() esegue la versione di Child (polimorfismo), ma p.label legge il campo di Parent, perché i campi vengono nascosti, non sovrascritti. La soluzione è semplice: tieni i campi private e accedi a loro solo tramite metodi, così vince sempre la chiamata polimorfica.

Prossima pagina: modificatori di accesso

Il polimorfismo funziona bene solo quando le sottoclassi possono vedere e sovrascrivere i membri giusti, mentre il resto del codice non può entrare e rompere le invarianti. Questo equilibrio è controllato dagli accessi public, protected, private e package-private: i modificatori di accesso, nella prossima pagina.

Domande frequenti

Cos'è il polimorfismo in Java?

Polimorfismo significa che un solo tipo riferimento può puntare a oggetti di tante classi diverse, e il metodo che viene eseguito davvero è scelto a runtime in base al tipo reale dell'oggetto, non al tipo dichiarato della variabile. Così una variabile Shape shape può contenere un Circle o un Square, e chiamare shape.area() esegue automaticamente la versione giusta.

Qual è la differenza tra overriding e overloading in Java?

L'overriding si ha quando una sottoclasse sostituisce un metodo della superclasse con la stessa firma: è ciò che rende possibile il polimorfismo a runtime. L'overloading si ha quando una classe ha più metodi con lo stesso nome ma liste di parametri diverse; il compilatore ne sceglie uno in fase di compilazione in base agli argomenti. L'overriding si risolve a runtime in base al tipo dell'oggetto; l'overloading in fase di compilazione in base ai tipi degli argomenti.

Qual è la differenza tra upcasting e downcasting in Java?

L'upcasting tratta un oggetto figlio come il suo tipo padre (Animal a = new Dog();): è sempre sicuro e di solito implicito. Il downcasting va nella direzione opposta (Dog d = (Dog) a;) ed è sicuro solo se l'oggetto è davvero di quel sottotipo; altrimenti lancia ClassCastException. Proteggi sempre ogni downcast con un controllo instanceof.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA