1. 概要

この短いチュートリアルでは、エラースーパータイプコンストラクターが呼び出される前に「X」を参照できませんを取得する方法と、それを回避する方法を示します。

2. コンストラクターチェーン

コンストラクターは、他のコンストラクターを1つだけ呼び出すことができます。 この呼び出しは、本文の最初の行にある必要があります。

キーワードthisを使用して同じクラスのコンストラクターを呼び出すことも、キーワードsuperを使用してスーパークラスのコンストラクターを呼び出すこともできます。

コンストラクターが別のコンストラクターを呼び出さない場合、コンパイラーはスーパークラスの引数なしコンストラクターへの呼び出しを追加します。

3. コンパイルエラー

このエラーは、コンストラクターチェーンを呼び出す前にインスタンスレベルのメンバーにアクセスしようとしたことに要約されます。

これに遭遇する可能性のあるいくつかの方法を見てみましょう。

3.1. インスタンスメソッドの参照

次の例では、5行目でスーパータイプコンストラクターが呼び出される前に「X」を参照できませんコンパイルエラーが表示されます。 コンストラクターがインスタンスメソッドgetErrorCode()の使用を早すぎることに注意してください。

public class MyException extends RuntimeException {
    private int errorCode = 0;
    
    public MyException(String message) {
        super(message + getErrorCode()); // compilation error
    }

    public int getErrorCode() {
        return errorCode;
    }
}

このエラーは、 u ntil super()がを完了するまで、クラスMyExceptionのインスタンスがないためです。 したがって、インスタンスメソッド getErrorCode()をまだ呼び出すことはできません。

3.2. インスタンスフィールドの参照

次の例では、インスタンスメソッドの代わりにインスタンスフィールドを使用した例外が表示されます。 インスタンス自体の準備が整う前に、最初のコンストラクターがインスタンスメンバーをどのように使用しようとするかを見てみましょう。

public class MyClass {

    private int myField1 = 10;
    private int myField2;

    public MyClass() {
        this(myField1); // compilation error
    }

    public MyClass(int i) {
        myField2 = i;
    }
}

インスタンスフィールドへの参照は、そのクラスが初期化された後、つまり this()または super()を呼び出した後にのみ行うことができます。

では、インスタンスフィールドも使用する2番目のコンストラクターにコンパイラエラーがないのはなぜですか?

すべてのクラスはクラスObjectから暗黙的に派生しているため、コンパイラによって暗黙的に super()呼び出しが追加されることに注意してください。

public MyClass(int i) {
    super(); // added by compiler
    myField2 = i;
}

ここで、 Object のコンストラクターは、 myField2 にアクセスする前に呼び出されます。つまり、問題はありません。

4. ソリューション

この問題に対する最初の可能な解決策は簡単です。2番目のコンストラクターを呼び出さない。最初のコンストラクターで、2番目のコンストラクターで実行したいことを明示的に実行します。

この場合、myField1の値をmyField2にコピーします。

public class MyClass {

    private int myField1 = 10;
    private int myField2;

    public MyClass() {
        myField2 = myField1;
    }

    public MyClass(int i) {
        myField2 = i;
    }
}

ただし、一般的には、構築しているものの構造を再考する必要があるでしょう。

ただし、コードの繰り返しを避けるためなど、正当な理由で2番目のコンストラクターを呼び出す場合は、コードをメソッドに移動できます:

public class MyClass {

    private int myField1 = 10;
    private int myField2;

    public MyClass() {
        setupMyFields(myField1);
    }

    public MyClass(int i) {
        setupMyFields(i);
    }

    private void setupMyFields(int i) {
        myField2 = i;
    }
}

繰り返しますが、これは、コンパイラがメソッドを呼び出す前にコンストラクタチェーンを暗黙的に呼び出したために機能します。

3番目の解決策は、静的フィールドまたはメソッドを使用することです。 myField1 を静的定数に変更すると、コンパイラーも満足します。

public class MyClass {

    private static final int SOME_CONSTANT = 10;
    private int myField2;

    public MyClass() {
        this(SOME_CONSTANT);
    }

    public MyClass(int i) {
        myField2 = i;
    }
}

フィールドをstaticにすることは、このオブジェクトのすべてのインスタンスと共有されることを意味することに注意してください。したがって、あまり軽くすることは変更ではありません。

static が正しい答えであるためには、強い理由が必要です。 たとえば、値は実際にはフィールドではなく定数である可能性があるため、staticおよびfinalにするのが理にかなっています。 呼び出したい構築メソッドは、クラスのインスタンスメンバーにアクセスする必要がない可能性があります。つまり、staticである必要があります。

5. 結論

この記事では、 super()または this()呼び出しの前にインスタンスメンバーを参照すると、コンパイルエラーが発生することを確認しました。 これは、明示的に宣言された基本クラスと、暗黙のObject基本クラスで発生することがわかりました。

また、これがコンストラクターの設計の問題であることを示し、コンストラクターでコードを繰り返すか、構築後のセットアップメソッドに委任するか、構築を支援するために定数値または静的メソッドを使用することで、これを修正する方法を示しました。 。

いつものように、この例のソースコードはGitHubにあります。