2013-03-10 16 views
11

Supponiamo di avere una classe Base astratta che implementa l'interfaccia Runnable.È corretto chiamare il metodo astratto dal costruttore in Java?

public abstract class Base implements Runnable { 

    protected int param; 

    public Base(final int param) { 
     System.out.println("Base constructor"); 
     this.param = param; 
     // I'm using this param here 
     new Thread(this).start(); 
     System.out.println("Derivative thread created with param " + param); 
    } 

    @Override 
    abstract public void run(); 
} 

E qui è una delle poche classi derivate.

public class Derivative extends Base { 

    public Derivative(final int param) { 
     super(param); 
    } 

    @Override 
    public void run() { 
     System.out.println("Derivative is running with param " + param); 
    } 

    public static void main(String[] args) { 
     Derivative thread = new Derivative(1); 
    } 

} 

Il punto è che voglio che la mia classe Base faccia delle cose generali invece di copiarle ogni volta. In realtà, è in esecuzione bene, l'uscita è sempre lo stesso:

costruttore di base filo derivativa creata con param 1 derivativo è in esecuzione con param 1

Ma è sicuro in Java per avviare una discussione chiamando il metodo astratto nel costruttore? Perché, in C++ e C# non è sicuro nella maggior parte dei casi, per quanto ne so. Grazie!

risposta

21

Questo codice dimostra il motivo per cui si dovrebbe mai chiamata di un metodo astratto, o qualsiasi altro metodo override, da un costruttore:

abstract class Super { 
    Super() { 
     doSubStuff(); 
    } 
    abstract void doSubStuff(); 
} 

class Sub extends Super { 
    String s = "Hello world"; 

    void doSubStuff() { 
     System.out.println(s); 
    } 
} 

public static void main(String[] args) { 
    new Sub(); 
} 

Quando eseguito, viene stampato null. Ciò significa che i soli metodi "sicuri" di avere in un costruttore sono privati ​​e/o finali.

D'altra parte, il tuo codice in realtà non chiama un metodo astratto da un costruttore. Invece, si passa un oggetto non inizializzato a un altro thread per l'elaborazione, il che è peggio, dal momento che il thread che si sta iniziando può avere priorità e deve essere eseguito prima che l'Base termini l'inizializzazione.

+1

Ci sarebbe qualcosa di improprio nell'avere che un costruttore chiami un metodo astratto o virtuale * il cui contratto specifica che può essere chiamato da un costruttore, deve essere sicuro chiamare da tale contesto, e può solo chiamare altri metodi che sono allo stesso modo sicuro*? – supercat

+1

@supercat: Suppongo che dipenda da quanto ti fidi della documentazione, da quanto ti fidi degli altri a seguire tale documentazione e da quanto ti fidi di chiunque possa estendere tale classe o sottoclasse di esso per propagarsi o ricordarsi in modo appropriato fare riferimento a tali avvertenze. Questa è una grande fiducia, secondo me. Preferisco le cose che possono essere dimostrate da test automatici e che non possono. –

+2

Abbastanza giusto. Il caso di utilizzo più grande che avevo in mente era per una situazione in cui una classe derivata avrebbe dovuto scavalcare un metodo per restituire una costante che deve essere uguale per tutte le istanze di ciascuna sottoclasse e che sarà necessaria nel costruttore e altrove, quindi un'implementazione tipica sarebbe 'int getWoozleForType() {return 23;}'. Passare una cosa del genere al costruttore e memorizzarlo in un campo di istanza sembra un po 'odioso, e non riesco a pensare ad altri approcci che sembrano invitanti. – supercat

3

È molto difficile chiamare un metodo astratto da un costruttore. I metodi richiamati dai costruttori dovrebbero sempre essere privati ​​o finali, per impedire l'override.

Vedi questo link per una domanda here

2
Non

una buona idea, dato che quando viene eseguito() viene richiamato, l'oggetto derivato potrebbe non essere stato inizializzato. Se run() dipende da qualsiasi stato in derivato, può fallire.

Nel tuo caso semplice funziona. Ma allora non ha senso per la sottoclasse. Si può semplicemente

public Base(final int param, Runnable action) { 

    new Thread(action).start(); 
1

Il passaggio di this al di fuori del costruttore si chiama "lasciando il this escape dal costruttore" e può causare alcuni bug particolarmente fastidiosi, perché l'oggetto potrebbe trovarsi in uno stato incoerente.

Questo è il caso in particolare quando this viene passato a un altro thread, come in questo esempio. A causa del diritto JVM di riordinare le istruzioni all'interno di un thread, è possibile ottenere un comportamento/stato non definito che si verifica.

Problemi correlati