Inferenza Automatica dei Tipi e Ereditarietà in Java
- L'inferenza del tipo delle variabili locali in Java è supportata dalla keyword
var
introdotta in JDK 10. - Il tipo inferito di una variabile locale si basa sul tipo dichiarato del suo inizializzatore, non sul tipo effettivo dell'oggetto referenziato.
- In una gerarchia di ereditarietà, se l'inizializzatore è del tipo della superclasse, il tipo inferito della variabile sarà quello della superclasse, anche se l'oggetto è un'istanza di una classe derivata.
- Questo comportamento può limitare l'accesso ai campi e ai metodi specifici delle classi derivate quando si utilizza l'inferenza del tipo.
Inferenza del Tipo delle Variabili Locali e Ereditarietà
Come spiegato nella lezione apposita, JDK 10 ha aggiunto l'inferenza del tipo delle variabili locali al linguaggio Java, funzionalità supportata dalla keyword contestuale var
.
È importante comprendere chiaramente come funzioni l'inferenza del tipo all'interno di una gerarchia di ereditarietà. Si ricordi che un riferimento alla superclasse può riferirsi a un oggetto della classe derivata, e questa caratteristica fa parte del supporto di Java al polimorfismo.
Tuttavia, è fondamentale ricordare che, quando si utilizza l'inferenza del tipo delle variabili locali, il tipo inferito di una variabile si basa sul tipo dichiarato del suo inizializzatore.
Pertanto, se l'inizializzatore è del tipo della superclasse, quello sarà il tipo inferito della variabile. Non ha importanza se l'oggetto effettivamente referenziato dall'inizializzatore è un'istanza di una classe derivata.
Per esempio, si consideri questo programma:
// Quando si lavora con l'ereditarietà, il tipo inferito è il tipo
// dichiarato dell'inizializzatore, che potrebbe non essere il tipo più
// derivato dell'oggetto referenziato dall'inizializzatore.
class MiaClasse {
// ...
}
class PrimaClasseDerivata extends MiaClasse {
int x;
// ...
}
class SecondaClasseDerivata extends PrimaClasseDerivata {
int y;
// ...
}
class InferenzaTipoEreditarieta {
// Restituisce un oggetto di tipo MiaClasse.
static MiaClasse ottieniOggetto(int quale) {
switch(quale) {
case 0: return new MiaClasse();
case 1: return new PrimaClasseDerivata();
default: return new SecondaClasseDerivata();
}
}
public static void main(String[] args) {
// Anche se ottieniOggetto() restituisce diversi tipi di
// oggetti all'interno della gerarchia di ereditarietà di MiaClasse,
// il suo tipo restituito dichiarato è MiaClasse. Di conseguenza,
// nei tre casi mostrati qui, il tipo delle
// variabili `var` viene inferito come MiaClasse, anche se
// vengono ottenuti oggetti di tipi derivati diversi.
// Qui, ottieniOggetto(0) restituisce un oggetto di tipo MiaClasse.
var mc = ottieniOggetto(0);
// In questo caso, viene restituito un oggetto di tipo PrimaClasseDerivata.
var mc2 = ottieniOggetto(1);
// Qui, viene restituito un oggetto di tipo SecondaClasseDerivata.
var mc3 = ottieniOggetto(2);
// Poiché i tipi di mc2 e mc3 sono inferiti
// come MiaClasse (perché il tipo restituito di ottieniOggetto()
// è MiaClasse), né mc2 né mc3 possono accedere ai campi
// dichiarati da PrimaClasseDerivata o SecondaClasseDerivata.
// mc2.x = 10; // Errore! MiaClasse non ha un campo x.
// mc3.y = 10; // Errore! MiaClasse non ha un campo y.
}
}
Inferenza del tipo e classi derivate
Nel programma, viene creata una gerarchia costituita da tre classi, in cima alla quale si trova MiaClasse
.
PrimaClasseDerivata
è una sottoclasse di MiaClasse
, e SecondaClasseDerivata
è una sottoclasse di PrimaClasseDerivata
.
Il programma poi utilizza l'inferenza del tipo per creare tre variabili, chiamate mc
, mc2
e mc3
, chiamando ottieniOggetto()
.
Il metodo ottieniOggetto()
ha un tipo restituito di MiaClasse
(la superclasse), ma restituisce oggetti di tipo MiaClasse
, PrimaClasseDerivata
o SecondaClasseDerivata
, a seconda dell'argomento passato.
Come mostrato, il tipo inferito è determinato dal tipo restituito del metodo ottieniOggetto()
, non dal tipo effettivo dell'oggetto ottenuto. Dunque, tutte e tre le variabili avranno tipo MiaClasse
.