Creazione di oggetti con new

Come sappiamo, un oggetto viene creato con la primitiva new, la cui sintassi è:

new <nomeClasse> (<parametri>);

La primitiva new fa concettualmente tre cose:

  • crea l'oggetto allocando la memoria necessaria;
  • invoca sull'oggetto il costruttore della classe <nomeClasse>, passandogli i <parametri> (la selezione del costruttore avviene in base alla firma, come una qualsiasi invocazione di metodo);
  • restituisce un puntatore all'oggetto creato.

Nel caso <nomeClasse> abbia una superclasse diversa da Object, le cose sono leggermente più complicate.




Struttura di oggetti di una sottoclasse

Abbiamo visto la struttura degli oggetti della classe Studente, che eredita da Persona:

Concettualmente, un oggetto di Studente può essere visto come un oggetto di Persona (contenente solo le variabili di istanza nome e indirizzo) esteso con le variabili d'istanza proprie di Persona.

Per questo motivo, la new invoca sull'oggetto appena creato non solo il costruttore di Studente, ma anche un costruttore della sua superclasse Persona.




Costruzione di oggetti di sottoclassi

Vediamo un esempio concreto. Il seguente programma CostruttoriEredAdd sfrutta tre classi tali che EredAdd_C estende EredAdd_B che estende EredAdd_A. Ogni classe ha un costruttore, che incrementa l'unica variabile d'istanza.

public class EredAdd_A {
    public int x = 0;     // unica variabile d'istanza
    EredAdd_A() { x = x + 1; }    //costruttore
}
public class EredAdd_B extends EredAdd_A {
    EredAdd_B() { x = x + 1; }    //costruttore
}
public class EredAdd_C extends EredAdd_B {
    EredAdd_C() { x = x + 1; }    //costruttore
}
/* Esempio di creazione di oggetto di una sottoclasse: 
   vengono invocati anche i costruttori delle 
   superclassi.
   (EredAdd_C estende EredAdd_B estende EredAdd_A)
*/

public class CostruttoriEredAdd {
    public static void main(String args[]) {
        EredAdd_C c = new EredAdd_C();
        System.out.println("Valore di c.x: " + c.x);
    }
}

Se lo eseguiamo, stamperà "Valore di c.x : 3", e questo dimostra che sul nuovo oggetto vengono invocati tutti e tre i costruttori.




Ordine di invocazione dei costruttori

In quale ordine vengono invocati i costruttori? Nel seguente programma CostruttoriEredOrd, analogo al precedente, ogni costruttore stampa una stringa corrispondente alla propria classe.

public class EredOrd_A {
    EredOrd_A() { System.out.println("Costruttore A"); }
}
public class EredOrd_B extends EredOrd_A {
    EredOrd_B() { System.out.println("Costruttore B"); }
}
public class EredOrd_C extends EredOrd_B {
    EredOrd_C() { System.out.println("Costruttore C"); }
}
/* Si mostra in che ordine vengono invocati i 
   costruttori delle superclassi quando si crea 
   un oggetto di una sottoclasse 
   (EredOrd_C estende EredOrd_B estende EredOrd_A)
*/

public class CostruttoriEredOrd {
    public static void main(String[] args) {
        new EredOrd_C();
    }
}

Eseguendo la classe, verrà stampato

    Costruttore A
    Costruttore B
    Costruttore C


 




Regole di invocazione dei costruttori

Le regole seguenti determinano in modo ricorsivo la sequenza completa dei costruttori che vengono invocati al momento della creazione di un oggetto:
  • Se l'oggetto è della classe Object, viene eseguito il costruttore default;

  • Se l'oggetto è di una qualunque altra classe,

    • Se la prima istruzione del costruttore invocato non è né un comando this(...) né un comando super(...), allora prima di eseguire il corpo del costruttore viene invocato il costruttore default della superclasse.

    • Se la prima istruzione del costruttore è un comando super(...), allora prima di eseguire il corpo viene invocato il costruttore della superclasse determinato dai parametri attuali di super(...).

    • Se la prima istruzione del costruttore è un comando this(...) allora prima di eseguire il corpo viene invocato il costruttore della stessa classe determinato dai parametri attuali.


Esercizio: osservare cosa succede compilando le classi Ered_A, Ered_B e Ered_C eseguendo poi CostruttoriEred. Sapete spiegare quali costruttori sono invocati e in quale ordine vengono invocati?




Esempio di errore comune

Consideriamo la seguente versione delle classi Persona e Studente:

public class PersonaErr { 
    String nome; 
    String indirizzo; 
    
    public PersonaErr(String nome) {  
        this.nome = nome; 
        this.indirizzo = ""; 
    } 
    
    public PersonaErr(String nome, String indirizzo) {  
        this(nome); 
        this.indirizzo = indirizzo; 
    } 

    <altri metodi>

}

public class StudenteErr extends PersonaErr { 

    int matricola; 
    String pianoDiStudio; 
    static int nextMatricola = 1; 
    
    public StudenteErr(String nome, String indirizzo) {
        this.nome = nome;
        this.indirizzo = indirizzo;
        this.matricola = nextMatricola ++;
        this.pianoDiStudio = "";
    }

    <altri metodi>

}

Se le compiliamo, viene segnalato il seguente errore:

javac  -d /home/bruni/classes/ -g StudenteErr.java
StudenteErr.java:7: cannot resolve symbol
symbol  : constructor PersonaErr  ()
location: class PersonaErr
    public StudenteErr(String nome, String indirizzo) {
                                                      ^
1 error

Infatti il costruttore default della classe PersonaErr non è disponibile. La soluzione corretta richiede l'uso di super(...), come visto precedentemente (oppure la definizione del costruttore senza argomenti per la classe PersonaErr).