29

Javaでは、スーパーコンストラクタを実行する前にフィールドを初期化する方法はありますか?私は、コンパイラによって拒否さを考え出すことができるスーパーコンストラクタを実行する前にフィールドを初期化しますか?

でも醜いハック:

class Base 
{ 
    Base(String someParameter) 
    { 
     System.out.println(this); 
    } 
} 

class Derived extends Base 
{ 
    private final int a; 

    Derived(String someParameter) 
    { 
     super(hack(someParameter, a = getValueFromDataBase())); 
    } 

    private static String hack(String returnValue, int ignored) 
    { 
     return returnValue; 
    } 

    public String toString() 
    { 
     return "a has value " + a; 
    } 
} 

注:私は代表団への継承から切り替わったときに問題が姿を消したが、私はまだ知っていただきたいと思います。

+1

フィールド 'a'をあらかじめ初期化しようとしていますか? – Woot4Moo

+1

私はあなたがこれを行うことができるとは思わない。クラス内で行う初期化(コンストラクタの外にあっても)は、 'super'呼び出しの後にすべてのコンストラクタに移動されます。したがって、スーパーコンストラクタは常にフィールドの初期化の前に実行されます。 –

+5

@FredOverflow 'a'は' Derived'でしかアクセスできないので、なぜ 'super()'が呼び出される前に初期化されるのが問題なのですか?直後に初期化しても、あなたの提供するサンプルに違いはありません(Baseコンストラクタからオーバーライドされたメソッドを呼び出す場合を除き、これはかなり嫌な臭いをします)。 – assylias

答えて

20

いいえ、これを行う方法はありません。

language specsによれば、super()コールが行われるまでインスタンス変数は初期化されません。

これらは、リンクから取られたクラスのインスタンス作成のコンストラクタステップ、中に実行手順は次のとおりです。

  1. は、このコンストラクタ呼び出しのための 変数新しく作成されたパラメータにコンストラクタの引数を割り当てます。
  2. このコンストラクタは(これを使用して)同じクラスの別のコンストラクタの明示的なコンストラクタ呼び出し (§8.8.7.1)で始まる場合、 は、コンストラクタの呼び出し を再帰的にこれらの同じ5つのステップを使用して引数とプロセスを評価します。そのコンストラクタ の呼び出しが突然完了した場合、この手順は同じ理由で突然 を完了します。それ以外の場合は、手順5に進みます。
  3. このコンストラクタは、明示的なコンストラクタ (これを使用して)同じクラス内の別のコンストラクタの呼び出しでは開始しません。 このコンストラクタがObject以外のクラスの場合、この コンストラクタは、 スーパークラスコンストラクタの明示的または暗黙的な呼び出しで開始されます(superを使用)。引数を評価し、 スーパークラスコンストラクタの呼び出しを再帰的に処理します。これは同じ5つのステップ を使用します。そのコンストラクタの呼び出しが を突然終了した場合、同じ手順で同じ手順を実行すると、この手順は突然完了します。 そうでなければ、それらが現れる 左から右の順に、対応するインスタンス変数をインスタンス変数 初期化子の値を割り当てるインスタンス初期化子とインスタンス変数初期化子このクラスの を、実行手順4
  4. に進みソースのテキストで クラスのコード。初期化子 のいずれかを実行すると例外が発生した場合、それ以上のイニシャライザは処理されません。 このプロシージャは、同じ例外を使用して突然終了します。 それ以外の場合は、手順5に進みます。
  5. このコンストラクタの残りの部分を実行します。その実行 が突然完了した場合、同じ手順で のためにこの手順は突然完了します。それ以外の場合、この手順は正常に完了します。
+0

これは確かですか?私はこれに遭遇した: _...Bがこれらのイベントを次の順序で参照する場合もあります(また、コンパイラはこのような命令を自由に並べ替えることもできます)。メモリの割り当て、リソースへの参照の割り当て、コンストラクタの呼び出し。メモリが割り当てられ、リソースフィールドが設定された後スレッドBが来ると仮定しますが、DCL記事の@BrianGoetzからコンストラクタが呼び出される前に、コンパイラは明らかに参照と呼び出しを割り当てるためのいくつかの最適化を行うことができますその後のコンストラクタ! – mkilic

1

Java language specification (section 8.8.7)によって禁止です:

コンストラクタ本体の最初の文は、同じクラスの、または直接 スーパークラスの他のコンストラクタの明示的な呼び出し かもしれません。

コンストラクタ本体は次のようになります。

ConstructorBody:他の人が言っている

{ ExplicitConstructorInvocationopt BlockStatementsopt } 
4

として、あなたはスーパークラスのコンストラクタを呼び出す前にインスタンスフィールドを初期化することはできません。

しかし、回避策があります。 1つは、値を取得してそれをDerivedクラスのコンストラクタに渡すファクトリクラスを作成することです。

class DerivedFactory { 
    Derived makeDerived(String someParameter) { 
     int a = getValueFromDataBase(); 
     return new Derived(someParameter, a); 
    } 
} 


class Derived extends Base 
{ 
    private final int a; 

    Derived(String someParameter, int a0) { 
     super(hack(someParameter, a0)); 
     a = a0; 
    } 
    ... 
} 
10

私はハッキングのこれらの種類を使用することをお勧めしませんスーパーコンストラクタは、どのような場合に実行されますが、私たちが「醜いハック」について話しているので、我々はこの

public class Base { 
    public Base() { 
     init(); 
    } 

    public Base(String s) { 
    } 

    public void init() { 
    //this is the ugly part that will be overriden 
    } 
} 

class Derived extends Base{ 

    @Override 
    public void init(){ 
     a = getValueFromDataBase(); 
    } 
} 

を利用することができます。

+0

私はそれがJavaでうまくいくとは思えません。参照を探しています... –

+0

それは動作し、テスト... –

+1

+1 - あなたは正しいです。 Ick。私はC++を考えていました。まだ - 私はハックが必要だとは思わない - 私の答えを参照してください。 –

4

私はこれを行う方法を得ました。

class Derived extends Base 
{ 
    private final int a; 

    // make this method private 
    private Derived(String someParameter, 
        int tmpVar /*add an addtional parameter*/) { 
     // use it as a temprorary variable 
     super(hack(someParameter, tmpVar = getValueFromDataBase())); 
     // assign it to field a 
     a = tmpVar; 
    } 

    // show user a clean constructor 
    Derived(String someParameter) 
    { 
     this(someParameter, 0) 
    } 

    ... 
} 
関連する問題