2009-03-19 5 views
10

In einem Java-Programm habe ich mehrere Unterklassen erben von einem Elternteil (das ist abstrakt). Ich wollte ausdrücken, dass jedes Kind ein einmal gesetztes Mitglied haben sollte (was ich vom Konstrukteur vorhatte). Mein Plan war es, Code zu schreiben. wie folgt:Warum kann ich in Java kein endgültiges Element (ohne Initialisierung) in der übergeordneten Klasse deklarieren und seinen Wert in der Unterklasse festlegen? Wie kann ich umgehen?

public abstract class Parent { 
    protected final String birthmark; 
} 

public class Child extends Parent { 
    public Child(String s) { 
     this.birthmark = s; 
    } 
} 

Allerdings scheint dies nicht die Java-Götter zu gefallen. In der Elternklasse bekomme ich die Nachricht, dass birthmark "möglicherweise nicht initialisiert wurde", in der Kindklasse bekomme ich "Das letzte Feld birthmark kann nicht zugegriffen werden".

Also, was ist der Java-Weg dafür? Was vermisse ich?

+0

Keine direkte Antwort, aber im Klassenformat können finale Instanzfelder nur von derselben Klassendatei gesetzt werden, aber nicht unbedingt im Konstruktor. IIRC. –

+0

Verwandte Frage: http://stackoverflow.com/questions/14383276/initialize-member-of-abstract-class-without-subclasses-having-write-access – user905686

Antwort

19

Sie können es nicht, weil, während die übergeordnete Klasse zu vergleichen, der Compiler nicht sicher sein kann, dass die Unterklasse initialisieren wird. Sie werden es in der übergeordneten Konstruktor initialisieren müssen, und haben das Kind den Konstruktor der Eltern nennen:

public abstract class Parent { 
    protected final String birthmark; 
    protected Parent(String s) { 
     birthmark = s; 
    } 
} 

public class Child extends Parent { 
    public Child(String s) { 
     super(s); 
     ... 
    } 
} 
+3

+1 für die Eltern ctor geschützt! –

+0

Wenn ich den Konstruktor des Kindes überspringen würde (und in diesem Fall wahrscheinlich den ctor des Elternteils öffentlich halten würde), könnte ich 'new Child ("set this") machen und bekommen, was ich will, richtig? –

7

Pass es den Mutter Konstruktor:

public abstract class Parent { 
    private final String birthmark; 
    public Parent(String s) { 
     birthmark = s; 
    } 
} 

public class Child extends Parent { 
    public Child(String s) { 
     super(s); 
    } 
} 
+0

In meinem Fall kann ich nicht übergeben, da das Objekt eine Referenz benötigt 'dieses', irgendein Tipp? –

2

Eine andere Java-ish Weg, dies zu tun, ist wahrscheinlich die übergeordnete Klasse haben eine abstrakte „Getter“ zu definieren, und lassen Sie die Kinder es umsetzen. Es ist in diesem Fall keine gute Möglichkeit, es zu tun, aber in einigen Fällen kann es genau das sein, was Sie wollen.

+0

Der Aufruf der abstrakten Getter-Methode im Konstruktor kann potenziell gefährlich sein. Eine gute Regel besteht jedoch darin, niemals eine ausschaltbare Methode direkt oder indirekt von einem Konstruktor aus aufzurufen. – TofuBeer

+0

@TofuBeer irgendein Beispiel, wie gefährlich das sein könnte? Diese Antwort funktioniert perfekt für mich [hier] (http://stackoverflow.com/questions/34822434/composite-inheritance-how-to-assign-a-final-field-at-sub-class-constructor-whic)! EDIT [mmm] (http://stackoverflow.com/questions/3404301/whats-wrong-with-overridable-method-calls-in-constructors) –

+0

Wenn Sie eine überschriebene Methode in einem Konstruktor aufrufen und wenn diese Methode auf eine Variable zugreift Von der Kindklasse wird diese Variable noch nicht initialisiert, da der Konstruktor der Kindklasse noch nicht instanziiert wurde. Es ist gefährlich, weil es jetzt funktionieren kann, aber wenn jemand den Getter später ändert, wird es abstürzen oder den falschen Wert bekommen. – TofuBeer

0

Ja, die letzten Mitglieder sollen in der Klasse zugeordnet werden, in der sie deklariert sind. Sie müssen einen Konstruktor mit einem String-Argument zu Parent hinzufügen.

0

Deklarieren Sie einen Konstruktor in der Superklasse, die von der Unterklasse aufgerufen wird. Sie müssen das Feld in der Oberklasse festlegen, um sicherzustellen, dass es initialisiert ist, oder der Compiler kann nicht sicher sein, dass das Feld initialisiert ist.

0

Wahrscheinlich möchten Sie einen Parent (String birthmark) -Konstruktor haben, damit Sie in Ihrer Elternklasse sicherstellen können, dass final immer initialisiert wird. Dann können Sie super (Muttermal) von Ihrem Konstruktor Child() aufrufen.

1

Warum nicht die Initialisierung einer Methode delegieren? Überschreiben Sie dann die Methode in der übergeordneten Klasse.

public class Parent { 
    public final Object x = getValueOfX(); 
    public Object getValueOfX() { 
     return y; 
    } 
} 
public class Child { 
    @Override 
    public Object getValueOfX() { 
    // whatever ... 
    } 
} 

Dies sollte benutzerdefinierte Initialisierung ermöglichen.

+1

Dieses Idiom ist eine wirklich schlechte Idee ... wenn ein Benutzer macht getValueofX() abhängig von einem Feld der Child-Klasse, gibt es eine NullPointerException, da das Kind noch nicht tatsächlich konstruiert wurde. Es kann funktionieren, wenn Sie sehr vorsichtig mit Docs sind, aber dumme/böswillige Benutzer können es innerhalb eines Herzschlags brechen. –

+0

@Chamelaeon: Das ist so ein blöder Kommentar, was würde ein böswilliger oder dummer Benutzer Code für Ihr Projekt schreiben? Ein böswilliger oder dummer Benutzer könnte System.exit (0) schreiben? –

+0

das wird [hier] helfen (http://stackoverflow.com/questions/34822434/composite-inheritance-how-to-assign-a-final-field-at-sub-class-constructor-whic), thx! –

2

Ich würde es tun, wie folgt:

public abstract class Parent 
{ 
    protected final String birthmark; 

    protected Parent(final String mark) 
    { 
     // only if this makes sense. 
     if(mark == null) 
     { 
      throw new IllegalArgumentException("mark cannot be null"); 
     } 

     birthmark = mark; 
    } 
} 

public class Child 
    extends Parent 
{ 
    public Child(final String s) 
    { 
     super(s); 
    } 
} 

final bedeutet, dass die Variable pro Instanz einmal initialisiert werden kann. Der Compiler kann nicht sicherstellen, dass jede Unterklasse die Zuweisung zum Muttermal bereitstellt, sodass die Zuweisung im Konstruktor der übergeordneten Klasse erzwungen wird.

Ich fügte die Überprüfung für null hinzu, nur um zu zeigen, dass Sie auch den Vorteil erhalten, die Argumente an einer Stelle anstatt an jedem Cosntructor überprüfen zu können.

+0

Gibt es einen Grund, die Parameter auch bei der Deklaration der Funktion final zu markieren? Der Compiler ließ mich ohne. –

+0

Ich mache es aus Gewohnheit ... stoppt Sie param = instanceVariable. Wenn der Hotspot jemals in der Lage ist, die finalen Vars besser zu optimieren (ich glaube nicht, dass es das jetzt tut), dann wird mein Code auch magisch schneller sein :-) – TofuBeer