3

ここにいくつかのサンプルコードがあります、

class Base
{
  private int val;

  Base() {
  val = lookup();
  }

  public int lookup() {
    //Perform some lookup
  // int num = someLookup();
  return 5;
  }

  public int value() {
  return val;
  }
}

class Derived extends Base
{
  private int num = 10;

  public int lookup() {
  return num;
  }
}


class Test
{
  public static void main(String args[]) {

  Derived d = new Derived();
  System.out.println("d.value() returns " + d.value());

  }
}

出力:d.value()は0を返します// lookup()がオーバーライドされるため、10を期待しましたが、0ではありません!誰かがこれを明確にすることができますか?

のインスタンス変数の初期化はDerived、ルックアップメソッドの実行時に発生していません。Derivedメソッドが呼び出されたときにのインスタンス変数が初期化されていることを確認するにはどうすればよいですか?

4

7 に答える 7

9

someLookupまず、メソッドがないため、そのコードはコンパイルされません。

とにかく、それとは別に、コンストラクターが階層的に実行される方法のために、あなたの期待が無効であることがあなたの問題であると私は信じています。

スーパークラスのコンストラクターは常にサブクラスの前に実行されます。これには、サブクラスの変数の初期化子が含まれます(実際にはコンストラクターの一部として実行されます)。したがって、のインスタンスを作成するとDerived、次のようになります。

  1. コンストラクターが最初Baseに呼び出されます。
  2. lookup()が呼び出され、の実装を使用しDerivedます。
  3. numが返されます。これは、Derivedのコンストラクターと初期化子が実行されていないため、この時点でのデフォルト値です
  4. val0に設定されます。
  5. Derivedイニシャライザーとコンストラクターが実行されます。この時点から呼び出すと、10lookupが返されます。

一般に、まさにこの理由から、コンストラクターから非最終メソッドを呼び出すことはお勧めできません。多くの静的分析ツールは、それに対して警告を発します。これは、構築中にオブジェクト参照をリークさせるのと似ており、クラスレベルの不変条件を無効にするインスタンスになってしまう可能性があります(この場合、Derivednumは「常に」10ですが、ある時点で0であることがわかります)。

編集:この特定のケースでは、追加のコードがなくてもnum、定数を作成することで問題を解決できることに注意してください。

class Derived extends Base
{
  private static final int num = 10;
  ...

静的初期化子はクラスがロードされるときに実行されるため(コンストラクターが呼び出される前に実行する必要があります)、これは実際に必要なことを実行します。ただし、これは次の場合に問題がないことを前提としています。

numa)同じ変数を共有するクラスのすべてのインスタンス。b)num変更する必要はありません(これが真の場合、(a)は自動的に真になります)。

あなたが与えた正確なコードでは、これは明らかに事実ですが、簡潔にするために余分な機能を省略している可能性があります。

これは、一般的な意味でのこの「問題」の回避策であるためではなく、比較と関心のためにここに含めます(そうではないため)。

于 2010-07-20T09:40:27.533 に答える
4

0が返される理由は、Derivedでnumに10が割り当てられる前に、コンストラクターBaseが呼び出されている(およびDerivedでルックアップを呼び出している)ためです。

一般的に、派生インスタンスフィールドが初期化される前に、ベースコンストラクターが呼び出されます。

于 2010-07-20T09:36:03.330 に答える
3

基本クラスの構築中にサブクラスフィールドにアクセスできない理由については、すでに多くの優れた回答がありますが、次のような方法を求めたと思います。

public abstract class Animal {
  public Animal() {
    System.println(whoAmI());
  }
  public abstract String whoAmI();
}

public Lion() extends Animal {
  private String iAmA = "Lion";
  public Lion(){super();}
  public String whoAmI() {return iAmA;}
}

実用的な方法は、基本クラスにinit()メソッドを導入し、次のようにサブクラスのコンストラクターから呼び出します。

public abstract class Animal {
  private boolean isInitialized = false;
  public Animal() {}
  void init() {
    isInitialized = true;
    System.out.println(whoAmI());
  }
  public abstract String whoAmI();
  public void someBaseClassMethod() {
    if (!isInitialized)
      throw new RuntimeException("Baseclass has not been initialized");
    // ...
  }
}

public Lion() extends Animal {
  private String iAmA = "Lion";
  public Lion() {
    super();
    init();
  }
  public String whoAmI() {return iAmA;}
}

唯一の問題は、サブクラスにinit()基本クラスのメソッドを強制的に呼び出させることができず、基本クラスが適切に初期化されない可能性があることです。しかし、フラグといくつかの例外を除いて、実行時にプログラマーに呼び出すべきだったことを思い出させることができますinit()...

于 2010-07-20T10:05:33.487 に答える
2

サブクラスでオーバーライドできるコンストラクターでメソッドを呼び出すことは、一般的に悪い考えです。あなたの例では、次のことが起こります。

  • 派生コンストラクターは呼び出されます
    • 基本コンストラクターは最初のアクションとして呼び出されます
    • 基本コンストラクターはルックアップを呼び出します
  • 派生コンストラクターは続行され、初期化はnumから10になります

基本コンストラクターがlookupを呼び出したときにサブクラスコンストラクターが終了していないため、オブジェクトはまだ完全には初期化されておらず、lookupはnumフィールドのデフォルト値を返します。

于 2010-07-20T09:43:48.153 に答える
2

ゆっくりやりましょう:

class Test
{
  public static void main(String args[]) {
  // 1
  Derived d = new Derived();
  // 2
  System.out.println("d.value() returns " + d.value());    
  }
}

ステップ1、Derivedで(デフォルトの)コンストラクターを呼び出します。num= 10に設定する前に、Derivedのルックアップメソッドを呼び出すBaseのコンストラクターにチェーンしますが、numが設定されていないため、valは初期化されません。

ステップ2、Baseに属するd.value()を呼び出し、valが1のために設定されていないため、10ではなく0を取得します。

于 2010-07-20T09:48:28.553 に答える
1

クラスlookup()のメソッドをオーバーライドしているため、コンストラクターが呼び出されると、本体の元のメソッドが呼び出されます。初期化の時点で、のインスタンス変数はまだ初期化されておらず、0です。そのため、のvalは0に割り当てられています。DerivedBaseDerivedreturn numBasenumDerivedBase

私があなたの意図を正しく理解した場合は、value方法を次のように変更する必要Baseがあります。

public int value() {
return lookup();
}
于 2010-07-20T09:38:58.487 に答える
1

以下のコードは、コンストラクターがこれを呼び出すときに0を返します(プログラムを見ると10を期待します)。単純な理由は、numがまだ初期化されておらず、親クラスがこのメソッドを呼び出すことです。

public int lookup() {
    return num;
}
于 2010-07-20T09:41:22.843 に答える