2008-08-07 16 views
33

Javaでは、コンストラクタの最初の行がsuperへの呼び出しである必要があります。暗黙的にsuper()を呼び出しているか、別のコンストラクタを明示的に呼び出していますか。私が知りたいことは、なぜその周りにtryブロックを置くことができないのでしょうか?私のsuper()コールの周りにtryブロックを使用できないのはなぜですか?

私の具体的なケースは、テスト用のモッククラスを持っていることです。デフォルトのコンストラクタはありませんが、テストをより簡単にするようにしたいと思います。私はまた、コンストラクタからスローされた例外をRuntimeExceptionにラップしたい。

public class MyClassMock extends MyClass { 
    public MyClassMock() { 
     try { 
      super(0); 
     } catch (Exception e) { 
      throw new RuntimeException(e); 
     } 
    } 

    // Mocked methods 
} 

しかし、Javaはスーパーが最初の文でないと文句を言い:

だから、私がやりたいことを効果的にこれです。

私の回避策:

public class MyClassMock extends MyClass { 
    public static MyClassMock construct() { 
     try { 
      return new MyClassMock(); 
     } catch (Exception e) { 
      throw new RuntimeException(e); 
     } 
    } 

    public MyClassMock() throws Exception { 
     super(0); 
    } 

    // Mocked methods 
} 

が、これが最善の回避策ですか?なぜJavaは私に前者をさせませんか?


Javaはモックを行うには、しかし...私は潜在的に矛盾した状態で構築されたオブジェクトを持たせたくないということである「なぜ」、私は気にしないととしての私の最高の推測そのことについて。私は上記を行うことができるはずです...少なくとも、私は上記が私の場合には安全であることを知っています...またはそれがとにかくあるべきであるかのように見えます。

私がテストしたクラスから使用するメソッドをオーバーライドしているため、初期化されていない変数を使用するリスクはありません。

+1

興味深いことに、これは純粋にJava言語の制限です。同等のバイトコードは完全に有効です。 – Antimony

+0

バイトコードはまだ有効ですか?私は、以下に説明する結果のセキュリティホールを誰かが悪用した後、無効になることを思い出します。 – Joshua

+0

ルールで許可されていないためです。 [JDK仕様](http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.10)をお読みください。たとえあなたがコンパイラを通過したとしても、検証者はそれを拒否します。 –

答えて

14

残念なことに、コンパイラは理論的な原則に基づいて作業することはできません。あなたのケースでは安全だと分かっていても、許可すればすべてのケースで安全でなければなりません。

つまり、コンパイラはあなただけを止めるわけではありません。安全ではなく、特別な処理が必要であることを知らない人も含めて、誰もが停止しています。おそらく、これには他の理由もあります。すべての言語では、対処方法が分かっていると、通常危険な方法があります。 C#.NETで

が同様の規定があり、そしてベースのコンストラクタを呼び出すコンストラクタを宣言するための唯一の方法はこれです:そうすることで

public ClassName(...) : base(...) 

、基本コンストラクタはの身体の前に呼び出されますこのオーダーを変更することはできません。

+1

キャッチブロックでこれを使用しないようにするだけではどうですか?これは、例外をラッピングする一般的なケースをカバーしています。 – Antimony

2

Javaが内部的にどのように実装されているのかわかりませんが、スーパークラスのコンストラクタが例外をスローすると、拡張するクラスのインスタンスはありません。たとえば、ほとんどの場合継承されるため、toString()またはequals()メソッドを呼び出すことは不可能です。

Javaでは、スーパークラスからすべてのメソッドをオーバーライドする場合、およびsuper.XXX()句を使用しない場合はコンストラクタ内のsuper()呼び出しをtry/catchすることができます。私にはあまりにも複雑な音です。

2

私はJavaの内部を深く理解することはできませんが、コンパイラが派生クラスをインスタンス化する必要がある場合は、最初にベース(およびその前のベース(。 ..))、サブクラスで作成された拡張をスラップします。

だから、まったく同じような変数やそのようなものの危険性さえありません。サブクラスのコンストラクタの中で、の基底クラス 'コンストラクタの前に何かをしようとすると、基本的にはまだ存在しない基本オブジェクトインスタンスを拡張するようにコンパイラに要求しています。

編集:あなたのケースでは、MyClassのは、ベースオブジェクトになり、MyClassMockはサブクラスです。

5

信頼できないコードから新しいSecurityManagerオブジェクトを作成しないようにするためです。

public class Evil : SecurityManager { 
    Evil() 
    { 
     try { 
     super(); 
     } catch { Throwable t } 
     { 
     } 
    } 
} 
6

これは古い質問ですが、私はそれが気に入っていたので、私は自分の答えを与えることにしました。なぜこれができないのか、私の理解はおそらくあなたの興味深い質問の議論と将来の読者に貢献するでしょう。

まず、オブジェクトの構築に失敗した例を示します。

ように、のは、クラスAを定義してみましょう:

class A { 
    private String a = "A"; 

    public A() throws Exception { 
     throw new Exception(); 
    } 
} 

今、我々はtry...catchブロック内のタイプAのオブジェクトを作成したいと仮定しましょう。

A a = null; 
try{ 
    a = new A(); 
}catch(Exception e) { 
    //... 
} 
System.out.println(a); 

明らかに、このコードの出力はnullとなります。

Javaが部分的に構成されたバージョンAを返さない理由は何ですか?結局のところ、コンストラクタが失敗する点では、オブジェクトのnameフィールドは既に初期化されています。

オブジェクトが正常に構築されなかったため、Javaは部分的に構築されたAのバージョンを返すことはできません。オブジェクトは矛盾した状態にあるため、Javaによって破棄されます。変数Aは初期化されていなくても、nullのままです。

あなたが知っているように、新しいオブジェクトを完全に構築するには、すべてのスーパークラスを最初に初期化する必要があります。スーパークラスの1つが実行に失敗した場合、オブジェクトの最終状態はどうなりますか?それを判断することは不可能です。

この精巧な例を見て

class A { 
    private final int a; 
    public A() throws Exception { 
     a = 10; 
    } 
} 

class B extends A { 
    private final int b; 
    public B() throws Exception { 
     methodThatThrowsException(); 
     b = 20; 
    } 
} 

class C extends B { 
    public C() throws Exception { super(); } 
} 

Cのコンストラクタが呼び出されるとBの初期化中に例外が発生した場合、何が最終int変数bの値でしょうか?

このように、オブジェクトCは作成できません。偽物です。ごみ箱です。完全に初期化されていません。

私の場合、これはあなたのコードが違法である理由を説明しています。

-1

これを回避する方法の1つは、プライベートな静的関数を呼び出すことです。 try-catchを関数本体に配置することができます。Javaはあなたがこれを行うことができない理由、具体的に答えるために、

public class Test { 
    public Test() { 
    this(Test.getObjectThatMightThrowException()); 
    } 
    public Test(Object o) { 
    //... 
    } 
    private static final Object getObjectThatMightThrowException() { 
    try { 
     return new ObjectThatMightThrowAnException(); 
    } catch(RuntimeException rtx) { 
     throw new RuntimeException("It threw an exception!!!", rtx); 
    } 
    } 
} 
+0

理由を詳しく教えてください。 – Unheilig

+0

私は少し精緻化しました。十分な? – aliteralmind

+0

ここでプライベートスタティック関数を呼び出すのはなぜですか?あなたが思っていたOPのコードには何が問題なのですか? – Unheilig

0

私はこの質問には、多くの答えを持って知っているが、私は、これは許可されません理由について私の小さなちらほらを与えたいと思います。だからここにあなたがsuper()は、サブクラスのコンストラクタで何かする前に呼び出す必要がありますので、あなたのsuper()呼び出しの周りtrycatchブロックを使用しなかった場合、ブロックはしなければならないことに注意してください、今...

を行きますスーパー場合

try { 
    super(); 
    ... 
} catch (Exception e) { 
    super(); //This line will throw the same error... 
    ... 
} 

()fails in theblock, it HAS to be executed first in theキャッチblock, so thatスーパーruns before anything in your subclassのコンストラクタを試してみてください。このように見えます。これにより、最初と同じ問題が残されます。例外がスローされた場合、それは捕捉されません。 (この場合はcatchブロックで再びスローされます)

ここでは、上記のコードはJavaによって許可されていません。このコードでは、最初のスーパーコールの半分を実行してから再度呼び出すことができます。これにより、いくつかのスーパークラスで問題が発生する可能性があります。

さて、例外がどこかに巻き込まれる可能性があるため、Javaがあなたの代わりにsuper()を呼び出す例外を投げることはできません理由は、プログラムがあなたのサブクラスのオブジェクトにsuper()を呼び出すことなくを続ける、とおそらく例外があなたのオブジェクトをパラメータとして受け取り、まだ初期化されていない継承されたインスタンス変数の値を変更しようとするからです。

関連する問題