Corso di Java

Ereditarietà e gerarchie di classi II

 

Se avete provato a compilare gli esempi della volta scorsa, ossia la classe “Uomo” e la classe “UomoDAffari”, vi sarete trovati di fronte ad almeno due errori del compilatore di questo tipo:



Come si può vedere, il compilatore Java ci da diverse informazioni riguardo ad ogni errore commesso:
il file sorgente in cui l’errore è stato rilevato;
il numero della riga;
il tipo dell’errore;
viene stampata l’intera riga e messo un simbolo (^) nel punto in cui l’errore è stato rilevato.
La prima informazione sembrerebbe inutile, tuttavia ha un senso, perché prima di compilare il sorgente indicato alla linea di comando (nel nostro caso UomoDAffari.java), Java cerca tutti i bytecode relativi alle classi utilizzate nel sorgente specificato, se non li trova cerca i sorgenti e (se li trova) li compila prima di quello da noi indicato.

In questo caso, la compilazione del sorgente Uomo.java (se non era stata fatta in precedenza) è andata a buon fine e non ha dato luogo ad alcun errore.
Il primo errore rilevato al compilatore è “No constructor matching Uomo() found in class Uomo” con riferimento al sorgente UomoDAffari.java.
La cosa può, a prima vista, sembrare piuttosto strana; tuttavia è chiaro che il problema è legato ai costruttori.
Abbiamo detto che non è necessario specificare un costruttore per una classe.
Quando si omette di definirlo esplicitamente, il compilatore si occupa di crearne automaticamente uno di default senza argomenti, cosicché per creare un oggetto di quella classe bisogna indicare il nome della classe, seguito da due parentesi tonde (aperta e chiusa) del tipo:

NomeClasse oggetto = new NomeClasse();

Questa istruzione crea un nuovo oggetto e invoca il costruttore di default, anche se non ne è stato definito uno in maniera esplicita; senza di esso non avremmo alcun metodo da chiamare per istanziare il nostro oggetto.
Teniamo presente che, una volta definito almeno un costruttore, il compilatore non creerà automaticamente quello di default (ossia senza parametri), per cui non potremo creare un oggetto della classe “Uomo” semplicemente scrivendo:

Uomo macho = new Uomo();

Poiché non abbiamo indicato alcun costruttore per la classe “UomoDAffari”, il compilatore ne costruisce uno di default e salta fuori un problema dovuto all’ereditarietà, che ci spiega il perché del primo errore.

Infatti quando si istanzia un oggetto di una sottoclasse, esso contiene quello che potremmo definire un “sottooggetto” della superclasse, che è a tutti gli effetti un oggetto della classe padre (parlare di superclasse o di classe padre è la stessa cosa).
E’ essenziale che il “sottooggetto” della classe padre sia inizializzato correttamente e ciò lo si può fare solamente effettuando l’inizializzazione nel costruttore, per mezzo di un’invocazione del costruttore della superclasse.
Java inserisce automaticamente nel costruttore della classe figlio una chiamata al costruttore senza argomenti della classe padre.
Ecco la causa dell’errore: il costruttore di default creato dal compilatore deve eseguire al suo interno l’invocazione al costruttore Uomo() che non è presente nella superclasse, per cui la compilazione non può avere luogo.
Due sono le soluzioni possibili:
possiamo definire esplicitamente un costruttore senza argomenti per la classe Uomo();
possiamo definire esplicitamente un costruttore per la classe “UomoDAffari”, che invochi esplicitamente il costruttore della superclasse.

La prima soluzione non ha senso per il nostro esempio (non sarebbe carino creare un uomo con un nome di default), per cui opteremo per la seconda e inseriremo nel codice della sottoclasse il seguente costruttore:

UomoDAffari (String nome, byte eta, String prof, String tds, long l) {
    super(nome, eta, prof);
    titoloDiStudio = tds;
    liquidita = l;
    }


Avete notato qualcosa di nuovo?
Ebbene, la parola chiave super viene utilizzata per riferirsi alla superclasse della classe attuale.
Con la prima istruzione non facciamo altro che invocare uno dei costruttori della classe “Uomo”, passandogli come parametri i valori opportuni, mentre le due istruzioni successive servono ad inizializzare gli attributi specifici della nostra sottoclasse.
Se riproviamo a compilare il primo errore sarà sparito; vedremo come correggere il secondo nella prossima puntata.

 

Torna all'indice Generale del corso di Corso di Java di Software Planet