41

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

注: 継承から委任に切り替えたときに問題はなくなりましたが、それでも知りたいです。

4

6 に答える 6

31

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

言語仕様によると、インスタンス変数はsuper()呼び出しが行われるまで初期化されません。

これらは、リンクから取得した、クラス インスタンス作成のコンストラクター ステップで実行されるステップです。

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

スーパー コンストラクターはどのような場合でも実行されますが、「最も醜いハック」について話しているので、これを利用できます。

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

この種のハックを使用することは決してお勧めしません。

于 2013-03-28T13:19:53.903 に答える
9

これを行う方法があります。

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

    ...
}
于 2013-09-06T12:46:58.513 に答える
8

他の人が言ったように、スーパークラス コンストラクターを呼び出す前にインスタンス フィールドを初期化することはできません。

しかし、回避策があります。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;
    }
    ...
}
于 2013-03-28T13:17:56.570 に答える
1

Java 言語仕様 (セクション 8.8.7) で禁止されています。

コンストラクター本体の最初のステートメントは、同じクラスまたは直接のスーパークラスの別のコンストラクターの明示的な呼び出しである場合があります。

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

コンストラクタ本体:

{ ExplicitConstructorInvocationopt BlockStatementsopt }
于 2013-03-28T13:06:15.997 に答える