Inferenza Automatica dei Tipi e Ereditarietà in Java

Concetti Chiave
  • 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.
    }
}
Consiglio

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.