2013-03-10 11 views
11

Angenommen, ich habe eine abstrakte Basisklasse, die die Runnable-Schnittstelle implementiert.Ist es OK, abstrakte Methode vom Konstruktor in Java aufzurufen?

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(); 
} 

Und hier ist eine der wenigen abgeleiteten Klassen.

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); 
    } 

} 

Der Punkt ist, dass ich einige allgemeine Sachen meine Basisklasse machen wollen, anstatt sie jedes Mal zu kopieren. Eigentlich ist es läuft gut, der Ausgang ist immer das gleiche:

Basiskonstruktor Derivative Thread mit param 1 erstellt Derivat mit param 1

laufen Aber ist es in Java sicher ein Thread der Aufruf zu starten abstrakte Methode im Konstruktor? Weil es in C++ und C# in den meisten Fällen unsicher ist, soweit ich weiß. Vielen Dank!

Antwort

21

Dieser Code zeigt, warum sollten Sie nie Aufruf eine abstrakte Methode oder jede andere überschreibbare Methode, von einem Konstruktor:

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(); 
} 

Wenn er gestartet wird, dies druckt null. Dies bedeutet, dass die einzigen "sicheren" Methoden in einem Konstruktor private und/oder endgültige sind.

Auf der anderen Seite ruft Ihren Code tatsächlich nicht eine abstrakte Methode von einem Konstruktor. Stattdessen übergeben Sie ein nicht initialisiertes Objekt an einen anderen Thread zur Verarbeitung, was noch schlimmer ist, da der Thread, den Sie starten, möglicherweise Priorität erhält und ausgeführt wird, bevor Ihr Base die Initialisierung beendet.

+1

Wäre es etwas Unpassendes, wenn ein Konstruktor eine abstrakte oder virtuelle Methode * aufruft, deren Vertrag angibt, dass er von einem Konstruktor aufgerufen werden kann, muss er aus einem solchen Kontext sicher aufrufen und nur andere ähnliche Methoden aufrufen sicher*? – supercat

+1

@supercat: Ich nehme an, das hängt davon ab, wie sehr Sie der Dokumentation vertrauen, wie sehr Sie anderen vertrauen, solchen Dokumentationen zu folgen, und wie sehr Sie jedem trauen, der jemals eine solche Klasse oder irgendeine Unterklasse erweitern könnte, um sich entsprechend weiterzuentwickeln oder daran zu erinnern beziehen Sie sich auf solche Warnungen. Das ist meiner Meinung nach sehr viel Vertrauen. Ich bevorzuge Dinge, die durch automatisierte Tests bewiesen werden können, und das kann nicht. –

+2

Fair genug. Der größte Anwendungsfall, den ich im Sinn hatte, war eine Situation, in der eine abgeleitete Klasse eine Methode überschreiben sollte, um eine Konstante zurückzugeben, die für alle Instanzen jeder Unterklasse gleich sein muss und die im Konstruktor und anderswo benötigt wird. Eine typische Implementierung wäre also 'int getWoozleForType() {return 23;} '. Eine solche Sache an den Konstruktor zu übergeben und sie in einem Instanzfeld speichern zu lassen, scheint ein bisschen eklig zu sein, und mir sind auch keine anderen Ansätze bekannt, die einladend erscheinen. – supercat

3

Es ist eine sehr schlechte Übung, eine abstrakte Methode von einem Konstruktor aufzurufen. Methoden, die von Konstruktoren aufgerufen werden, sollten immer privat oder endgültig sein, um ein Überschreiben zu verhindern.

Siehe diesen Link auf eine Frage, da here

2

keine gute Idee, wenn run() aufgerufen wird, kann das Derivat Objekt nicht initialisiert wurde. Wenn run() von einem Zustand in Derivative abhängt, kann es fehlschlagen.

In Ihrem einfachen Fall funktioniert es. Aber dann gibt es keinen Sinn für die Unterklasse. Sie können einfach

public Base(final int param, Runnable action) { 

    new Thread(action).start(); 
1

Das Übergeben von this aus dem Konstruktor heißt "Lassen this Escape vom Konstruktor" und kann zu einigen besonders böse und seltsame Bugs führen, weil das Objekt in einem inkonsistenten Zustand sein kann.

Dies ist besonders der Fall, wenn this an einen anderen Thread übergeben wird, wie in diesem Beispiel. Aufgrund des JVM-Rechtes, Anweisungen innerhalb eines Threads neu zu ordnen, können Sie ein undefiniertes Verhalten/einen Zustand erreichen.

Verwandte Themen