インスタンス - Javaで基本クラスのコンストラクタがオーバーライドされたメソッドを呼び出すときの派生クラスオブジェクトの状態
java 自 クラス インスタンス 化 (3)
Derivedクラスオブジェクトがまだ作成されていないと、オーバーライドされたメソッドが呼び出されるのはなぜですか?
Derived
クラスコンストラクターは、暗黙的にBase
クラスコンストラクターを最初のステートメントとして呼び出します。 Base
クラスコンストラクタは、そのオブジェクトが作成されているクラスであるため、 Derived
クラスのオーバーライドされた実装を呼び出すmethod()
を呼び出します。 Derived
クラスのmethod()
は、その時点でvar
を0と見なします。
どの時点でvarに値0が割り当てられますか?
Derived
クラスのint
が呼び出される前に、 var
はint
型のデフォルト値、つまり0が割り当てられます。 暗黙のスーパークラスコンストラクタ呼び出しが終了した後で 、 Derived
クラスのコンストラクタ内のステートメントの実行が始まる前に 、値2が割り当てられます。
そのような動作が望ましいユースケースはありますか?
一般的に、非final
クラスのコンストラクタ/初期化子で非final
非private
メソッドを使用するのは悪い考えです。 その理由はあなたのコードで明らかです。 作成されているオブジェクトがサブクラスのインスタンスである場合、メソッドは予期しない結果をもたらす可能性があります。
下記のJavaコードを参照してください。
class Base{
Base(){
System.out.println("Base Constructor");
method();
}
void method(){}
}
class Derived extends Base{
int var = 2;
Derived(){
System.out.println("Derived Constructor");
}
@Override
void method(){
System.out.println("var = "+var);
}
}
class Test2{
public static void main(String[] args) {
Derived b = new Derived();
}
}
見られる出力は次のとおりです。
Base Constructor
var = 0
Derived Constructor
派生オブジェクトは半分初期化されているため、var = 0が発生すると思います。 Jon Skeetがここで言うことと同じ
私の質問は:
Derivedクラスオブジェクトがまだ作成されていないと、オーバーライドされたメソッドが呼び出されるのはなぜですか?
どの時点でvarに値0が割り当てられますか?
そのような動作が望ましいユースケースはありますか?
この動作を説明するために注意すべきJava言語仕様のいくつかの特性があります。
- スーパークラスのコンストラクタは、常にサブクラスのコンストラクタの前に暗黙的または明示的に呼び出されます。
- コンストラクターからのメソッド呼び出しは、他のメソッド呼び出しとまったく同じです。 メソッドが非最終メソッドの場合、呼び出しは仮想呼び出しです。つまり、呼び出すメソッド実装は、オブジェクトの実行時型に関連付けられたものです。
- コンストラクタの実行前に、すべてのデータメンバはデフォルト値(数値プリミティブの場合は0、オブジェクトの場合はnull、ブール値の場合はfalse)で自動的に初期化されます。
イベントの順序は次のとおりです。
- サブクラスのインスタンスが作成されます
- すべてのデータメンバはデフォルト値で初期化されます
- 呼び出されたコンストラクタは、ただちに関連スーパークラスのコンストラクタに制御を委任します。
- スーパーコンストラクタはそれ自身のデータメンバの一部または全部を初期化してから仮想メソッドを呼び出します。
- メソッドはサブクラスによってオーバーライドされるため、サブクラスの実装が呼び出されます。
- メソッドはサブクラスのデータメンバを使用しようとしますが、それらはすでに初期化されていると仮定しますが、そうではありません - 呼び出しスタックはまだサブクラスのコンストラクタに戻りません。
つまり、スーパークラスのコンストラクターが非最終メソッドを呼び出すたびに、このトラップに入る危険性があるため、実行することはお勧めできません。 あなたがこのパターンを主張するならば、優雅な解決策がないことに注意してください。 これは2つの複雑で創造的なもので、どちらもスレッド同期を必要とします(!)。
Derived
オブジェクトが作成されました - それはコンストラクタがまだ実行されていないということです。 オブジェクトの型は、それが作成された瞬間(Javaがすべてのコンストラクタが実行される前に起こる)の後に決して変わることはありません。オブジェクトを作成するプロセスの一部として、コンストラクタが実行される前に、
var
デフォルト値の0が割り当てられます。 基本的に、型参照は設定され、オブジェクトを表すメモリの残りの部分はゼロに消去されます(概念的にはとにかく、すでにガベージコレクションの一部としてすでにゼロに消去されている可能性があります)。この振る舞いは少なくとも一貫性につながりますが、痛みを伴う可能性があります。 一貫性の観点から、可変基底クラスの読み取り専用サブクラスがあるとします。 基本クラスは、事実上trueにデフォルト設定されていた
isMutable()
プロパティを持つことができます - しかし、サブクラスはそれをオーバーライドして常にfalseを返します。 サブクラスのコンストラクタが実行される前にオブジェクトが変更可能であっても、その後不変であることは奇妙なことです。 その一方で、そのクラスのコンストラクタが:(を実行する前に、そのクラスでコードを実行してしまうという状況では間違いなく奇妙です。
いくつかのガイドライン:
コンストラクタ内であまり作業をしないようにしてください。 これを回避する1つの方法は、静的メソッドで作業を行い、次に静的メソッドの最後の部分を単純にフィールドを設定するコンストラクター呼び出しにすることです。 もちろん、これは仕事をしている間は多態性の恩恵を受けることができないことを意味します - しかしコンストラクター呼び出しでそうすることはとにかく危険でしょう。
コンストラクター中に非最終メソッドへの呼び出しを避けるように一生懸命にしてください - 混乱を引き起こす可能性が非常に高いです。 初期化が完了する前にそれらがオーバーライドされることがそれらが呼ばれることを知っているように、あなたが本当に明確にしなければならないどんなメソッド呼び出しでも文書化しなさい。
あなたが構築中にメソッドを呼び出さなければならない場合、 通常それはその後それを呼び出すことは適切ではありません。 その場合は、それを文書化し、名前の中にそれを示すようにしてください。
最初の段階で継承を使いすぎないようにしてください - Object以外のスーパークラスから派生したサブクラスがある場合にのみ問題になります。継承のための設計は難しいです。