Data la gerarchia (c'è anche una interfaccia come esempio):
interface SuperficieCalcolabile {
double calcolaSuperficie();
}
abstract class Solido implements SuperficieCalcolabile { /*blabla*/ }
class Cubo extends Solido { /*blabla e implementazione di calcolaSuperficie()*/ }
class Sfera extends Solido { /*blabla e implementazione di calcolaSuperficie()*/ }
A fronte di:
Sfera sfera1 = new Sfera( ....... );
TUTTI i seguenti assegnamenti sono leciti e funzionanti:
Solido s = sfera1;
SuperficieCalcolabile sc = sfera1;
Object o = sfera1;
Non c'è bisogno di mettere un cast esplicito
(tipo) (metterlo è comunque lecito) perché i tipi delle 3 variabili sono
super-tipi di Sfera. Questo il compilatore "lo sa" dalle definizioni dei tipi e mette un up-cast
implicito, che è assolutamente innocuo e non causa alcun check a runtime.
Il down-cast invece è "controllato" a runtime.
SuperficieCalcolabile sc = new Sfera( ....... );
Sfera sf = (Sfera) sc; // Ok
Questo cast è obbligatorio perché Sfera è un sotto-tipo di SuperficieCalcolabile. Il cast è lecito per il compilatore perché Sfera è in relazione con SuperficieCalcolabile. Detto in altro modo, il compilatore "sa" che una variabile di tipo SuperficieCalcolabile
potrebbe fare riferimento ad un oggetto Sfera.
C'è però un check a runtime in cui la JVM verifica materialmente: "l'oggetto referenziato da sc è realmente un Sfera?". In questo caso sì, quindi il cast ha successo e sf farà riferimento all'oggetto Sfera che puoi usare in seguito.
MA
SuperficieCalcolabile sc = new Cubo( ....... );
Sfera sf = (Sfera) sc; // ti becchi ClassCastException a runtime
Anche questo è lecito per il compilatore, ma il cast a runtime fallisce, perché l'oggetto realmente è di tipo Cubo, NON Sfera.