this()とsuper()をコンストラクターの最初のステートメントにする必要があるのはなぜですか?



Why Must This Super Be First Statement Constructor



Javaでは、コンストラクターでthis()またはsuper()を呼び出す場合、それが最初のステートメントである必要があります。どうして?

例えば:



public class MyClass { public MyClass(int x) {} } public class MySubClass extends MyClass { public MySubClass(int a, int b) { int c = a + b super(c) // COMPILE ERROR } }

Sunコンパイラは、「superの呼び出しはコンストラクタの最初のステートメントでなければならない」と述べています。 Eclipseコンパイラーは、「コンストラクター呼び出しはコンストラクターの最初のステートメントでなければならない」と述べています。

ただし、いくつかのコードを再配置することで、この問題を解決できます。



public class MySubClass extends MyClass { public MySubClass(int a, int b) { super(a + b) // OK } }

これは別の例です:

public class MyClass { public MyClass(List list) {} } public class MySubClassA extends MyClass { public MySubClassA(Object item) { // Create a list that contains the item, and pass the list to super List list = new ArrayList() list.add(item) super(list) // COMPILE ERROR } } public class MySubClassB extends MyClass { public MySubClassB(Object item) { // Create a list that contains the item, and pass the list to super super(Arrays.asList(new Object[] { item })) // OK } }

したがって、この あなたを止めません スーパーを呼び出す前に 実行ロジック 。これは、単一の式に含めることができないロジックを実行できないようにするだけです。

転送this()同様のルールがあります。コンパイラーは、「これへの呼び出しはコンストラクターの最初のステートメントでなければなりません」と言います。



コンパイラにこれらの制限があるのはなぜですか?コード例を挙げてください。コンパイラにこの制限がない場合、何か悪いことが起こりますか?


#1階

constructor サブクラスconstructor呼び出す前。これにより、コンストラクターの親クラスでメソッドを呼び出した場合に、親クラスが正しく設定されていることが保証されます。

スーパーコンストラクターに引数を渡すことは完全に合法です。操作の実行中にこれらの引数をインラインで作成するか、コンストラクターに渡してからsuperに渡す必要があります。 :

public MySubClassB extends MyClass { public MySubClassB(Object[] myArray) { super(myArray) } }

コンパイラーがこの操作を強制しない場合は、以下を実行できます。

public MySubClassB extends MyClass { public MySubClassB(Object[] myArray) { someMethodOnSuper() //ERROR super not yet constructed super(myArray) } }

場合parentクラスにはデフォルトのコンストラクタがあり、compiler superの呼び出しが自動的に挿入されます。 JavaのすべてのクラスはObjectから継承するため、したがって、オブジェクトコンストラクターを何らかの方法で呼び出す必要があり、最初に実行する必要があります。これを実現するために、コンパイラは自動的にsuper()を挿入します。 superを最前線に表示し、コンストラクター本体を正しい順序で実行するように強制します。つまり、オブジェクト->親->子-> ChildOfChild-> SoOnSoForth


#2階

これは、(a)部分的に構築されたオブジェクトの使用を許可されないようにするため、および(b)親のコンストラクターに「オブジェクトを新たに構築する」ように強制するためであると確信しています(Java仕様に精通している人)。

「悪い」ことのいくつかの例は次のとおりです。

class Thing { final int x Thing(int x) { this.x = x } } class Bad1 extends Thing { final int z Bad1(int x, int y) { this.z = this.x + this.y // WHOOPS! x hasn't been set yet super(x) } } class Bad2 extends Thing { final int y Bad2(int x, int y) { this.x = 33 this.y = y super(x) // WHOOPS! x is supposed to be final } }

#3階

子のコンストラクターを呼び出す前に、匿名初期化ブロックを使用して子のフィールドを初期化できます。この例では、次のことを示します。

public class Test { public static void main(String[] args) { new Child() } } class Parent { public Parent() { System.out.println('In parent') } } class Child extends Parent { { System.out.println('In initializer') } public Child() { super() System.out.println('In child') } }

これは出力します:

親の間で
イニシャライザで
子供たちの間で


#4階

JLSがそう言ったからです。 それを可能にするために互換性のある方法でJLSを変更することは可能ですか? 正しい。

ただし、これは言語仕様を複雑にし、十分に複雑ではありません。これはあまり便利なことではなく、多くの解決策があります(静的メソッドまたはラムダ式の結果を使用するthis(fn())別のコンストラクターを呼び出す-このメソッドは別のコンストラクターの前に呼び出されるため、スーパーコンストラクターでもあります)。したがって、修正されたパワーウェイトレシオは不利である。

このルールだけでは、スーパークラスの構築が完了するまでフィールドの使用が妨げられないことに注意してください。

これらの違法な例を考えてみましょう。

super(this.x = 5) super(this.fn()) super(fn()) super(x) super(this instanceof SubClass) // this.getClass() would be /really/ useful sometimes.

この例は合法ですが、「間違っています」。

class MyBase { MyBase() { fn() } abstract void fn() } class MyDerived extends MyBase { void fn() { // ??? } }

上記の例では、MyDerived.fn need MyDerivedコンストラクターのパラメーターを使用する必要がありますThreadLocalそれらを非表示にします。 ((

ちなみに、Java1.4には外部合成フィールドが含まれているのでthis内部クラスのスーパーコンストラクターが呼び出される前に割り当てられます。これにより、以前のバージョンのターゲットNullPointerExceptionイベント用にコンパイルされたコードに特別な問題が発生しました。

また、安全でない出版物が存在する場合、予防措置を講じない限り、他のスレッドを通じて構造を確認できることにも注意してください。

2018年3月の編集: ニュースで 記録:建設と検証 Oracleは、この制限を削除することを提案しています(ただし、C#とは異なり、thisコンストラクタチェーンの前になります) 確かに割り当てられていません (の))。

歴史的に、this()またはsuper()は最初にコンストラクターになければなりません。この制限はこれまで普及したことがなく、恣意的であると見なされています。この制限には、invokespecialの検証など、多くの微妙な理由があります。長年にわたり、仮想マシンレベルでこれらの問題を解決してきたため、この制限を解除することを検討することは、レコードだけでなく、すべてのコンストラクターに対しても実行可能です。


#5階

これが相続の哲学だからです。そして、Java言語仕様によれば、これはコンストラクターの本体を定義する方法です。

ConstructorBody:{ExplicitConstructorInvocationオプトBlockStatementsオプト}

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

  • 同じクラスの別のコンストラクターを明示的に呼び出す(キーワード「this」を使用して)または
  • 直接スーパークラスの明示的な呼び出し(キーワード「super」を使用)

コンストラクター本体が明示的なコンストラクター呼び出しで始まらず、宣言されたコンストラクターが元のクラスObjectの一部でない場合、コンストラクター本体は暗黙的にスーパークラスコンストラクター呼び出しで始まります 'super()'コンストラクター呼び出しはその直接のスーパークラスです。パラメータなし。などなど。オブジェクトのコンストラクタに至るまで、コンストラクタのチェーン全体が存在します。「Javaプラットフォームのすべてのクラスはオブジェクトの子孫です。」このことは 'と呼ばれます コンストラクタリンク 」。

なぜ今なのか?
JavaがConstructorBodyをこのように定義する理由は、オブジェクトの階層を維持する必要があるためです。継承の定義はカリキュラムを拡張することを忘れないでください。そうは言っても、存在しないものを拡張することはできません。最初にベース(スーパークラス)を作成する必要があり、次にそれを派生させることができます(サブクラス)。これが彼らが彼らを「親と子供」と呼ぶ理由です。あなたは親なしでは子供を持つことができません。

技術的には、サブクラスはその親クラスからすべてのメンバー(フィールド、メソッド、ネストされたクラス)を継承します。また、コンストラクターはメンバーではないため(オブジェクトではありません。オブジェクトの作成を担当します)、サブクラスに継承されませんが、呼び出すことはできます。そしてなぜなら オブジェクトの作成時に実行されるコンストラクターは1つだけです 。では、サブクラスオブジェクトを作成するとき、スーパークラスを作成することをどのように保証しますか?したがって、「リンクの構築」の概念は、現在のコンストラクターから他のコンストラクター(つまりスーパー)を呼び出すことができます。 Javaでは、階層を維持し、階層を確保するために、この呼び出しをサブクラスコンストラクターの最初の行にする必要があります。親オブジェクトを最初に明示的に作成しない場合(忘れた場合のように)、暗黙的に作成されると想定しています。

このチェックはコンパイル中に行われます。しかし、Javaがコンパイルエラーをスローしない場合、サブクラスのコンストラクター内の基本コンストラクターから基本コンストラクターを明示的に実行しようとすると、実行時に何が発生し、どのような実行時エラーが発生するかはわかりません。最初の行からではなく、本体...


#6階

私の推測では、これは、Javaコードを処理するツールを作成する人やJavaコードを読む人にとって簡単にするために行われていると思います。

許可されている場合super()またはthis()モバイルに電話をかけるときは、さらに変更を確認する必要があります。たとえば、if super()またはthis()呼び出して条件に移動if()暗黙的にsuper()挿入elseするのに十分賢くなければならない場合があります。 2回電話する場合super()または一緒に使用するsuper()this() 、エラーを報告する方法を知る必要があるかもしれません。これまでのところ、呼び出しsuper()またはthis()まで、受信側でのメソッド呼び出しを禁止し、いつ複雑になるかを把握する必要がある場合があります。

みんなにこれらの余分な仕事をさせることは、支払うよりも費用がかかるようです。


#7階

したがって、これは、superを呼び出す前にロジックを実行することを妨げるものではありません。これは、単一の式に含めることができないロジックを実行できないようにするだけです。

実際、複数のメソッドを使用してロジックを実行し、コードを静的関数でラップしてから、スーパーステートメントで呼び出すことができます。

あなたの例を使用してください:

public class MySubClassC extends MyClass { public MySubClassC(Object item) { // Create a list that contains the item, and pass the list to super super(createList(item)) // OK } private static List createList(item) { List list = new ArrayList() list.add(item) return list } }

#8階

Tldr:

他の答えは問題の「原因」を解決します。私はこの制限のためにいくつかを提供します スキル

基本的な考え方は、埋め込まれた文を使用することです ハイジャック superステートメント。これは、ステートメントを次のように偽装することで実行できます。 行われなければ。

Tsdr:

電話をかけていると考えてくださいsuper()Statement9()実行Statement1() super()

public class Child extends Parent { public Child(T1 _1, T2 _2, T3 _3) { Statement_1() Statement_2() Statement_3() // and etc... Statement_9() super(_1, _2, _3) // compiler rejects because this is not the first line } }

もちろん、コンパイラはコードを拒否します。したがって、これを行うことができます。

// This compiles fine: public class Child extends Parent { public Child(T1 _1, T2 _2, T3 _3) { super(F(_1), _2, _3) } public static T1 F(T1 _1) { Statement_1() Statement_2() Statement_3() // and etc... Statement_9() return _1 } }

唯一の制限は、 親クラスには、少なくとも1つのパラメーターを持つコンストラクターが必要です。 式を式に忍び込ませることができるように。

これはより詳細な例です:

public class Child extends Parent { public Child(int i, String s, T1 t1) { i = i * 10 - 123 if (s.length() > i) { s = 'This is substr s: ' + s.substring(0, 5) } else { s = 'Asdfg' } t1.Set(i) T2 t2 = t1.Get() t2.F() Object obj = Static_Class.A_Static_Method(i, s, t1) super(obj, i, 'some argument', s, t1, t2) // compiler rejects because this is not the first line } }

次のように作り直されました:

// This compiles fine: public class Child extends Parent { public Child(int i, String s, T1 t1) { super(Arg1(i, s, t1), Arg2(i), 'some argument', Arg4(i, s), t1, Arg6(i, t1)) } private static Object Arg1(int i, String s, T1 t1) { i = Arg2(i) s = Arg4(s) return Static_Class.A_Static_Method(i, s, t1) } private static int Arg2(int i) { i = i * 10 - 123 return i } private static String Arg4(int i, String s) { i = Arg2(i) if (s.length() > i) { s = 'This is sub s: ' + s.substring(0, 5) } else { s = 'Asdfg' } return s } private static T2 Arg6(int i, T1 t1) { i = Arg2(i) t1.Set(i) T2 t2 = t1.Get() t2.F() return t2 } }

実際、コンパイラーはこのプロセスを自動化できます。彼らはそうしないことを選んだだけです。


#9階

私は解決策を見つけました。

これはコンパイルされません:

public class MySubClass extends MyClass { public MySubClass(int a, int b) { int c = a + b super(c) // COMPILE ERROR doSomething(c) doSomething2(a) doSomething3(b) } }

これは機能します:

public class MySubClass extends MyClass { public MySubClass(int a, int b) { this(a + b) doSomething2(a) doSomething3(b) } private MySubClass(int c) { super(c) doSomething(c) } }

#10階

私はパーティーに少し遅れていることを知っていますが、私はこのテクニックを数回使用しました(そしてそれは少し珍しいことを知っています):

メソッドを使用して共通のインターフェースを作成しますInfoRunnable

public T run(Object... args)

コンストラクターに渡す前に何かを行う必要がある場合は、次のようにします。

super(new InfoRunnable() { public ThingToPass run(Object... args) { /* do your things here */ } }.run(/* args here */))

#11階

子オブジェクトを作成する前に、まず親オブジェクトを作成する必要があります。ご存知のように、そのようなクラスを書くとき:

public MyClass { public MyClass(String someArg) { System.out.println(someArg) } }

次へ移動(拡張および超非表示):

public MyClass extends Object{ public MyClass(String someArg) { super() System.out.println(someArg) } }

まず、a Objectを作成します次に、オブジェクトをMyClassに展開します。できませんObject以前に作成MyClass 。単純なルールは、親コンストラクターを子コンストラクターの前に呼び出す必要があるということです。しかし、クラスが複数のコンストラクターを持つことができることはわかっています。 Javaでは、呼び出すコンストラクターを選択できます(super()またはsuper(yourArgs...)になります)。したがって、次のように記述しますsuper(yourArgs...)コンストラクターを再定義します。コンストラクターは、親オブジェクトを作成するために呼び出されます。オブジェクトがまだ存在しないため、他のメソッドを実行する前にsuper()できません(ただし、super()では、オブジェクトが作成され、必要な操作を実行できます)。

では、なぜどのメソッドの後で実行できないのでしょうかthis()何ですか?ご存知のように、this()は現在のクラスのコンストラクターです。同様に、クラス内に異なる数のコンストラクターを含めることができ、this()またはthis(yourArgs...)そのように呼び出します。私が言ったように、各コンストラクターには隠されたメソッドがありますsuper() 。カスタムを書くときsuper(yourArgs...)使うときsuper(yourArgs...)削除super() super(yourArgs...) 。同様に、this()またはthis(yourArgs...)を定義すると、現在のコンストラクターでも削除されますsuper() if super() vsus this()同じメソッドで一緒に使用すると、複数の親オブジェクトが作成されます。それが正しい理由ですthis()メソッドが同じルールを課す理由。親オブジェクトの作成を別の子コンストラクターに渡すだけです。これはsuper()コンストラクターが親を作成します。したがって、コードは実際には次のようになります。

public MyClass extends Object{ public MyClass(int a) { super() System.out.println(a) } public MyClass(int a, int b) { this(a) System.out.println(b) } }

他の人が言っているように、次のコードを実行できます。

this(a+b)

次のコードを実行することもできます。

public MyClass(int a, SomeObject someObject) { this(someObject.add(a+5)) }

ただし、メソッドがまだ存在しないため、次のようなコードを実行することはできません。

public MyClass extends Object{ public MyClass(int a) { } public MyClass(int a, int b) { this(add(a, b)) } public int add(int a, int b){ return a+b } }

さらに、this()メソッドチェーン内super()コンストラクターである必要があります。次のようなオブジェクトを作成することはできません。

public MyClass{ public MyClass(int a) { this(a, 5) } public MyClass(int a, int b) { this(a) } }

#12階

class C { int y,z C() { y=10 } C(int x) { C() z=x+y System.out.println(z) } } class A { public static void main(String a[]) { new C(10) } }

例を参照してください。コンストラクターを呼び出す場合C(int x)最初の行で呼び出さなかった場合、zの値はyに依存しますC() 、その後、zに問題が発生します。 zは正しい値を取得しません。


#13階

コンストラクターが派生順に実行を完了することは理にかなっています。スーパークラスはサブクラスを理解しないため、実行する必要のある初期化は、サブクラスによって実行される初期化とは別のものであり、前提条件となる場合があります。したがって、最初に実行を完了する必要があります。

簡単なデモ:

class A { A() { System.out.println('Inside A's constructor.') } } class B extends A { B() { System.out.println('Inside B's constructor.') } } class C extends B { C() { System.out.println('Inside C's constructor.') } } class CallingCons { public static void main(String args[]) { C c = new C() } }

このプログラムの出力は次のとおりです。

Inside A's constructor Inside B's constructor Inside C's constructor

#14F

実際には、super()これはコンストラクターの最初のステートメントです。サブクラスが構築される前に、そのスーパークラスが完全に形成されていることを確認する必要があるためです。最初のステートメントがそうでない場合でもsuper()そしてコンパイラーもあなたのためにそれを追加します!


#15F

コード例を挙げてください。コンパイラにこの制限がない場合、何か悪いことが起こりますか?

class Good { int essential1 int essential2 Good(int n) { if (n > 100) throw new IllegalArgumentException('n is too large!') essential1 = 1 / n essential2 = n + 2 } } class Bad extends Good { Bad(int n) { try { super(n) } catch (Exception e) { // Exception is ignored } } public static void main(String[] args) { Bad b = new Bad(0) // b = new Bad(101) System.out.println(b.essential1 + b.essential2) } }

構築中の例外は、ほとんどの場合、構築中のオブジェクトを正しく初期化できず、現在エラー状態にあり、使用できず、ガベージコレクションする必要があることを意味します。ただし、サブクラスのコンストラクターは、その親クラスの1つで発生した例外を無視して、部分的に初期化されたオブジェクトを返すことができます。上記の例では、new Bad()パラメータnew Bad()に指定されている場合0または100より大きい場合、essential1 with essential2 Noneは正しく初期化されません。

例外を無視することは常に悪い考えだと言うかもしれません。わかりました、ここに別の例があります:

class Bad extends Good { Bad(int n) { for (int i = 0 i

おかしいですね。この例では、いくつのオブジェクトを作成しますか? 1?二?多分何も...

コンストラクターの途中で呼び出しを許可するsuper()またはthis()パンドラの箱を開く厄介なコンストラクター。


一方、super()またはthis()以前は静的なパーツを含める必要があったことを私は知っています。これは依存しないものである可能性がありますthis引用されたコード(実際には、this参照はコンストラクターの先頭にすでに存在しますが、super()またはthis()はできません戻る前に順番に使用されます)、そのような呼び出しが必要です。さらに、他のメソッドと同様に、super()またはthis()を呼び出すことができます。いくつかのローカル変数は前に作成する必要があります。

この場合、次の機会があります。

  1. 使用する この答えでは 表示モードはこの制限を回避できます。
  2. Javaチームがpre- super()およびpre- this()コードを許可するのを待ちます。これは、super()またはthis()によって実行できます。これは、コンストラクターに表示される可能性のある場所に制限を課すことによって実行されます。実際、今日のコンパイラーでさえ、コンストラクターの先頭に静的コードを安全に追加できる程度に、良い状況と悪い状況(または潜在的に悪い状況)を区別できます。確かに、super() with this() return this Referenceとすると、コンストラクターは
return this

最後に。そしてコンパイラはコードを拒否します

public int get() { int x for (int i = 0 i 0) x = y return x } public int get(boolean b) { int x try { x = 1 } catch (Exception e) { } return x }

エラー「変数xが初期化されていない可能性があります」、可能性がありますthis変数の実行this 、他のローカル変数と同じように、チェックしてください。唯一の違いはthis他のメソッドでは指定できないsuper()またはthis()呼び出し(通常、コンストラクトにそのような呼び出しがない場合、super()暗黙的に挿入されます最初にコンパイラによって)、2回割り当てられない場合があります。質問がある場合(たとえば、最初のget()、実際にx常に割り当てられている)、コンパイラーはエラーを返す場合があります。これはどのsuper()またはthis()よりも優れています。前のコメントを除いて、コンストラクターで単にエラーを返す方が適切です。


#16階

これは、コンストラクターが他のコンストラクターに依存しているためです。コンストラクターが正しく機能するためには、他のコンストラクターに依存する他の通常の関数が不可欠です。これが、依存関係コンストラクターを最初にチェックする必要がある理由です。これは、コンストラクターのthis()またはsuper()によって呼び出されます。 this()またはsuper()を介して呼び出された他のコンストラクターに問題がある場合、呼び出されたコンストラクターが失敗するとすべてのステートメントが失敗するため、他のステートメントの実行はどうなりますか。


#17階

制限が厳しすぎることに完全に同意します。静的補助メソッド(Tom Hawtin-viscous行で提案されている)を使用したり、すべての「pre-super()計算」をパラメーター内の単一の式にプッシュしたりすることが常に可能であるとは限りません。次に例を示します。

class Sup { public Sup(final int x_) { //cheap constructor } public Sup(final Sup sup_) { //expensive copy constructor } } class Sub extends Sup { private int x public Sub(final Sub aSub) { /* for aSub with aSub.x == 0, * the expensive copy constructor is unnecessary: */ /* if (aSub.x == 0) { * super(0) * } else { * super(aSub) * } * above gives error since if-construct before super() is not allowed. */ /* super((aSub.x == 0) ? 0 : aSub) * above gives error since the ?-operator's type is Object */ super(aSub) // much slower :( // further initialization of aSub } }

Carson Myersが示唆したように、「unbuilt object」例外を使用すると役立ちますが、オブジェクトをビルドするたびにこの例外をチェックすると、実行速度が低下します。言語仕様が複雑になったとしても、Javaコンパイラーが(ifステートメントを無効にするのではなく、パラメーターで?演算子の使用を許可するのではなく)より適切に区別できることを願っています。


#18階

理由を尋ねると、他の回答imoは、スーパーコンストラクターを呼び出すことができる理由を実際には述べていませんが、それが最初の行である場合に限ります。その理由はあなたが本当にそうではないからです 転送 コンストラクタ。 C ++では、同等の構文は次のとおりです。

MySubClass: MyClass { public: MySubClass(int a, int b): MyClass(a+b) { } }

このような初期化句を見ると、中括弧の前に特別であることがわかります。コンストラクターの残りの部分が実行される前、実際にはメンバー変数が初期化される前に実行されます。 Javaでも違いはありません。一部のコード(他のコンストラクター)を、コンストラクターが実際に開始する前、およびサブクラスのメンバーが初期化される前に実行する方法があります。その場合、最初の行に「call」(たとえばsuper)を置きます。 (ある意味では、superまたはthis最初の開始ブラケットの少し前です。後で入力しても、すべてを完全に構築する前に実行されるためです。)(例int c = a + b)コンパイラに「ああ、わかりました。他のコンストラクタがなくても、すべてを初期化できます」と言わせます。そのため、実行を開始してスーパークラスやメンバーなどを初期化し、中括弧コードの後に​​実行を開始します。

数行後に、「ああ、はい、このオブジェクトを作成するときに、これは基本クラスのコンストラクターに渡してほしいパラメーターです」というコードが表示された場合、手遅れであり、意味がありません。したがって、コンパイラエラーが発生します。


#19F

コンストラクターと静的メソッドをリンクすることで、この問題の解決策を見つけました。私がやりたいことは次のようになります。

public class Foo extends Baz { private final Bar myBar public Foo(String arg1, String arg2) { // ... // ... Some other stuff needed to construct a 'Bar'... // ... final Bar b = new Bar(arg1, arg2) super(b.baz()): myBar = b } }

したがって、基本的にはコンストラクターのパラメーターに基づいてオブジェクトを作成し、そのオブジェクトをメンバーに格納して、そのオブジェクトのメソッド結果をスーパーコンストラクターに渡します。クラスの性質は一定であるため、メンバーを最終メンバーにすることも重要です。たまたま、Barの構築には実際にはいくつかの中間オブジェクトが必要であるため、実際のユースケースでは、単一のフォームに縮小することはできません。

私はそれを次のように機能させることになりました:

public class Foo extends Baz { private final Bar myBar private static Bar makeBar(String arg1, String arg2) { // My more complicated setup routine to actually make 'Bar' goes here... return new Bar(arg1, arg2) } public Foo(String arg1, String arg2) { this(makeBar(arg1, arg2)) } private Foo(Bar bar) { super(bar.baz()) myBar = bar } }

スーパーコンストラクターを呼び出す前に複数のステートメントを実行するタスクを完了する法典。