4

スーパーコンストラクターが実行された後にのみインスタンスメンバーにアクセスできるという本を読みました。

私は次のコードに出くわしました:

class Parent {

    Parent() {
        printIt();
    }

    void printIt() {
        System.out.println("I'm in a overridden method. Great.");
    }
}

class Child extends Parent {

    int i = 100;

    public static void main(String[] args) {
        Parent p = new Child();
        p.printIt();
    }

    void printIt() {
        System.out.print(i + " ");
    }
}

そしてそれは印刷します:

0100

私の質問は次のようになります:

スーパーコンストラクターの実行後にのみインスタンスメンバーにアクセスできる場合、ParentクラスのprintIt()メソッド(実際にはポリモーフィズムのためにChildのprintIt())を実行すると、初期化されていないものにアクセスできたのはなぜですか?親のコンストラクターがまだ実行を終了していなくても、子のインスタンス変数i?

私は何が欠けていますか?

4

5 に答える 5

8

スーパーコンストラクターが実行された後にのみインスタンスメンバーにアクセスできるという本を読みました。

あなたの本は間違っています(それが本当に言っているのなら)。建設が始まると、いつでもアクセスできます。ただし、スーパーコンストラクターが実行されるまで初期化されません。したがって、印刷したのはデフォルト値でした:null、ゼロ、またはfalse。

于 2012-08-27T05:23:52.057 に答える
4

Parentのコンストラクターがまだ実行を終了していなくても、Childの初期化されていないインスタンス変数iにアクセスできましたか?

あなたはそれにアクセスすることができましたが、それが初期化される前に(これはあなたが通常望むものではありません)。

変数の「スペース」はすでに配置されていますが(結局のところインスタンスがあります)、変数を適切な開始値に初期化するコードはまだ実行されていません。したがって、すべてnull、false、および0になります。

その結果、クラス内のメソッド( "printIt")は、オブジェクトのライフサイクルの厄介なポイントで呼び出されます(初期化子が実行される前、 "half-finished"インスタンスで)。これはあなたが読んだ警告が言いたかったことです。

于 2012-08-27T05:20:20.810 に答える
2

あなたの例はあなたを誤解させていると思います。実際、スーパーコンストラクターは以前に実行されており、以下のような変更された例でこれを確認できます。また、説明として、メンバー値にはアクセスできますが、まだ初期化されていない可能性があります。

class Parent {

    int i = 0;

    Parent() {
        i = 1;
        printIt();
    }

    void printIt() {
        System.out.println("I'm in a overridden method. Great. i = " + i);
    }
}

class Child extends Parent {
    public static void main(String[] args) {
        Parent p = new Child();
        p.printIt();
    }

    void printIt() {
        System.out.print(i + " ");
    }
}
于 2012-08-27T05:28:03.603 に答える
0

オーバーライドはコードで行われます。オブジェクトは実行時に考慮されます。そこで、ChildのprintIt()が呼び出されました。現時点では、「i」の値は不明ですが、インスタンス変数であるため、デフォルト値は「0」です。それが完了すると、p.printIt()が呼び出され、ChildのprintIt()が呼び出され、この時点でint i = 100が読み取られ、100が出力されます。

したがって、出力は0100になるはずです

于 2012-08-27T05:27:55.203 に答える
0

オブジェクトのフィールドは、オブジェクトが最初に作成されたときにデフォルト値のnullまたは0に初期化され、次にコンストラクターが実際に実行されます。これにより、スーパーコンストラクターが最初のステップとして呼び出されます。

残念ながら、コンストラクターを次のように記述してこれを回避することはできません。

Child() {
  i=100;
  super();
}

iそして、これを行うことができなければ、親コンストラクターからのオーバーライドメソッド呼び出しで使用される前に子フィールドを設定する方法はありません。

ただし、これを回避するいくつかの方法を知っておく価値があります。

アプローチではi、抽象ゲッターの背後に隠れて、オーバーライドする新しいインスタンスを作成する静的ファクトリ関数を提供しますgetI

public class Child extends Parent {

   protected abstract getI();

   @Override void printIt() {
     System.out.print("i = " + i);
   }

   static Child create(final int i) {
      return new Child() {
         int getI() { return i; }
      }
   }
}

Child child = Child.create(100);

別のアプローチはprintIt、親/子のheirachyからを切り離すことです。次に、Parentコンストラクターが呼び出される前にプリンターを作成できます。(多くの場合、この種のトリックを使用して、Childを完全に削除し、Parentクラスとコンポーネントのみを残すことができます。つまり、継承ではなく構成を使用することになります。)

class Parent {
   public interface Printer {
     void printIt();
   }

   public class DefaultPrinter extends Printer {
     @Override void printIt() { 
       System.out.println("Default Printer...");
     }
   }

   Parent() {
     this(new DefaultPrinter());
   }

   Parent(Printer p ) {
     this.printer = p;
     printIt();
   }

   void printIt() {
     p.printIt();
   }
}

public class Child extends Parent {
   public class ChildPrinter implements Parent.Printer {
     final int i = 100;
     @Override void printIt() {
       System.out.println("i = "+i);
     }
   }

   Child() {
     super( new Printer() );
   }
}
于 2014-07-29T03:17:51.937 に答える