Archivi delle etichette: java class

Problemi con il costruttore


Malgrado il titolo, non sto ristrutturando il bagno.
E no, non parlerò dei problemi della famiglia Ligresti.
Semmai parlerò di programmazione, stavolta in Java. Il post è un pochino tecnico, cercherò di renderlo semplice, ma se cominciate ad annoiarvi siete autorizzati a saltare alla conclusioni.

L’errore

Qualche tempo fa mi si presenta uno studente con un programma Java che gli andava in crash. Cercherò qui di farvi capire qual’è il problema con un piccolo esempio esplicativo (giusto per non farvi vedere la tonnellata di codice del programma originale, che non serve a niente). Ecco il mio programmino:

class Base {
    public Base() {
        System.out.println("Base constructor");
        function();
    }
    public void function() {
        System.out.println("Base.function()");
    }
}

class Derived extends Base {
    public Derived(String n) {
        super();
        System.out.println("Derived constructor");
    }
    public void function() {
        System.out.println("Derived.function()");
    }
}

public class ConstructorDemo {
    public static void main(String args[]) {
        Derived obj = new Derived();
        System.out.println("Fine del programma!");
    }
}

Se volete provarlo, salvate il file con nome “ConstructorDemo”, compilate con “javac ConstructorDemo.java”, ed eseguite con “java ConstructorDemo”. Otterrere il seguente output:


Base constructor
Derived.function()
Derived constructor
Fine del programma!

Notate l’ordine delle chiamate: quando cerchiamo di creare un oggetto di tipo “Derived”, per prima cosa viene chiamato il costruttore della classe Derived, che a sua volta per prima cosa chiama il costruttore della classe Base (con super()), il quale stampa a video la prima scritta.
Questo costruttore della classe Base chiama la funzione “function()” che è ridefinita nella classe Derived: quindi, entra in gioco il polimorfismo dei linguaggi Object Oriented, per cui tra le due funzioni che si chiamano “function()” quella chiamata è la seconda che stampa la seconda scritta. Infine, si ritorna nel costruttore Derived(), che stampa a video la terza scritta, e nel main che stampa l’ultima scritta.
Spero mi abbiate seguito fin qui.
Ebbene, questo programma è bacato. Cioè, se lo eseguite funziona, niente di male. Ma chi ha scritto la classe Base è una testa d’uovo e ha fatto un errore di progetto della classe. Infatti, un costruttore non dovrebbe mai chiamare una funzione che potrebbe essere in seguito essere ridefinita da una classe derivata! Perché l’oggetto nel suo complesso non è stato ancora costruito del tutto, e in particolare ci sono parti ancora non inizializzate. Per metter in evidenza il problema farò due piccole modifiche alla classe Derived. Ecco la nuova versione:

class Base {
    public Base() {
        System.out.println("Base constructor");
        function();
    }

    public void function() {
        System.out.println("Base.function()");
    }
}

class Derived extends Base {
    private String name; 
    public Derived(String n) {
        super();
        name = n;
        System.out.println("Derived constructor");
    }
    public void function() {
        System.out.println("Derived.function() - " + name.toUpperCase());
    }
}

public class ConstructorDemo {
    public static void main(String args[]) {
        Derived obj = new Derived("Giuseppe");
        System.out.println("Fine del programma!");
    }
}

Adesso il costruttore della classe Derived dichiara una variabile privata di tipo String, e ha un parametro sempre di tipo String. La variabile name viene inizializzata nel costruttore per essere uguale al parametro n. Inoltre, la funzione “function()” adesso, oltre a stampare la scritta “Derived.function()”, stampa anche il contenuto della stringa name convertito in maiuscole. Ed ecco l’output del programma:

Base constructor
Exception in thread "main" java.lang.NullPointerException
at Derived.function(ConstructorDemo.java:22)
at Base.(ConstructorDemo.java:6)
at Derived.(ConstructorDemo.java:17)
at ConstructorDemo.main(ConstructorDemo.java:28)

Penso sia abbastanza chiaro cosa sia successo: il costruttore Base() chiama la funzione “function()” che usa la variabile name prima che tale variabile venga inizializzata nel costruttore Derived, e quindi bum.
L’errore non è in chi ha scritto la classe Derived(), ma in chi ha scritto la classe Base() chiamando la “function()” nel costruttore. Naturalmente in questo caso è sempre il sottoscritto ad aver scritto entrambe le classi, ma in generale non è detto.

Chi è stato?


Ma non è stato lo studente a fare l’errore. Infatti, lui aveva appena seguito un corso di programmazione Android, la classe “Base()” faceva parte dell’SDK, e la classe Derived era stata scritta da lui per il progetto del corso. E naturalmente non poteva immaginare che chi aveva scritto l’SDK avesse fatto un errore così madornale. Più precisamente la classe Base era la android.widget.SimpleCursorTreeAdapter. Per fortuna il problema è stato successivamente risolto in una successiva release.
Ma è davvero un problema così grave? Beh, se uno studente fa una cosa così al mio esame di OOSD, io sarei tentato di bocciarlo, o quantomeno abbassargli parecchio il voto. Sì, per me è piuttosto grave.

Conclusioni

Possibile che a Google facciano delle cavolate così? Beh, sì, è possibile, l’errore è sempre dietro l’angolo e non sempre i programmatori delle grosse aziende come Google sono “top of the notch”.
E quindi la prima lezione di questo post è: sempre … cioè no, mai (cit.) chiamare funzioni polimorfiche dal costruttore, a meno che la classe non sia dichiarata final (ovvero non derivabile). Idem in C++ o in ogni altro linguaggio OO.
E la seconda è: non sopravvalutate mai l’abilità dei programmatori degli altri! Meditate gente, meditate …