JavaがThrowableのジェネリックサブクラスを許可しないのはなぜですか?


146

Java Language Sepecification、第3版によると:

ジェネリッククラスがの直接または間接サブクラスである場合、コンパイル時エラーになりますThrowable

この決定がなされた理由を理解したいと思います。一般的な例外の何が問題になっていますか?

(私が知る限り、ジェネリックは単にコンパイル時の構文糖でありObject.classファイル内でとにかく翻訳されるので、ジェネリッククラスを効果的に宣言することは、すべてがであるかのように行われますObject。間違っている場合は修正してください。)


1
ジェネリック型の引数は、デフォルトでObjectである上限に置き換えられます。リストのようなものがあれば<?A>を拡張し、Aがクラスファイルで使用されます。
Torsten Marek、

@Torsten、ありがとうございます。以前はそのようなことを考えていませんでした。
Hosam Aly、

2
これは良いインタビューの質問です。
skaffman 2009

@TorstenMarek:を呼び出してもmyList.get(i)、明らかにをget返しますObject。コンパイラーは、A実行時に制約の一部を取り込むためにキャストを挿入しますか?そうでない場合、OPは正しくObject、実行時に最終的にs に要約されます。(クラスファイルには確かにに関するメタデータが含まれていますAが、これはメタデータAFAIKのみです。)
Mihai Danila

回答:


155

マークが言ったように、タイプはreifiableではありません、それは次の場合の問題です:

try {
   doSomeStuff();
} catch (SomeException<Integer> e) {
   // ignore that
} catch (SomeException<String> e) {
   crashAndBurn()
}

両方SomeException<Integer>SomeException<String>同じタイプに消去される、JVMは例外インスタンスを区別するための方法、及びその言うことは決してありませんcatch実行されるべきブロック。


3
「reifiable」とはどういう意味ですか?
aberrant80

61
そのため、ルールは「ジェネリック型はThrowableをサブクラス化できません」ではなく、「catch句は常にraw型を使用する必要があります」でなければなりません。
Archie

3
同じタイプの2つのcatchブロックを一緒に使用することを許可しないだけで済みます。したがって、SomeExc <Integer>を単独で使用することは合法であり、SomeExc <Integer>とSomeExc <String>を一緒に使用することのみが違法になります。それは問題にならないでしょうか、それともでしょうか?
ViliamBúr2013

3
ああ、今私はそれを手に入れました。私の解決策は、宣言する必要のないRuntimeExceptionsで問題を引き起こすでしょう。そのため、SomeExcがRuntimeExceptionのサブクラスである場合、SomeExc <Integer>をスローして明示的にキャッチできますが、他の関数が何の通知もなくSomeExc <String>をスローしていて、SomeExc <Integer>のキャッチブロックが誤ってそれをキャッチしてしまう可能性があります。
ViliamBúr2013

4
@ SuperJedi224-いいえ。それは正しく行われます- ジェネリックに下位互換性がなければならないという制約があるためです。
スティーブンC

14

例外の使用方法の簡単な例を次に示します。

class IntegerExceptionTest {
  public static void main(String[] args) {
    try {
      throw new IntegerException(42);
    } catch (IntegerException e) {
      assert e.getValue() == 42;
    }
  }
}

TRyステートメントの本文は、指定された値で例外をスローし、catch句によってキャッチされます。

対照的に、パラメーター化された型を作成するため、次の新しい例外の定義は禁止されています。

class ParametricException<T> extends Exception {  // compile-time error
  private final T value;
  public ParametricException(T value) { this.value = value; }
  public T getValue() { return value; }
}

上記をコンパイルしようとすると、エラーが報告されます。

% javac ParametricException.java
ParametricException.java:1: a generic class may not extend
java.lang.Throwable
class ParametricException<T> extends Exception {  // compile-time error
                                     ^
1 error

この制限は賢明です。なぜなら、そのような例外をキャッチしようとするほとんどすべての試みは失敗しなければならないからです。これは、型が再変換可能ではないためです。例外の一般的な用途は次のようなものであると期待できます。

class ParametricExceptionTest {
  public static void main(String[] args) {
    try {
      throw new ParametricException<Integer>(42);
    } catch (ParametricException<Integer> e) {  // compile-time error
      assert e.getValue()==42;
    }
  }
}

これは許可されていません。catch節のタイプは再設定できないためです。この記事の執筆時点では、Sunコンパイラはそのような場合に一連の構文エラーを報告しています。

% javac ParametricExceptionTest.java
ParametricExceptionTest.java:5: <identifier> expected
    } catch (ParametricException<Integer> e) {
                                ^
ParametricExceptionTest.java:8: ')' expected
  }
  ^
ParametricExceptionTest.java:9: '}' expected
}
 ^
3 errors

例外はパラメトリックにすることはできないため、構文は制限されているため、型は識別子として記述する必要があり、後続のパラメーターはありません。


2
reifiableとはどういう意味ですか?「reifiable」は言葉ではありません。
ForYourOwnGood

1
私は言葉自分自身を知りませんでしたが、Googleでクイック検索が私にこの得た:java.sun.com/docs/books/jls/third_edition/html/...
Hosamアリー


13

それは本質的にそれが悪い方法で設計されたからです。

この問題により、きれいな抽象的なデザインが妨げられます。

public interface Repository<ID, E extends Entity<ID>> {

    E getById(ID id) throws EntityNotFoundException<E, ID>;
}

ジェネリックが具体化されていない場合、catch句が失敗するという事実はその言い訳にはなりません。コンパイラーは、Throwableを拡張する具体的なジェネリック型を単に許可しないか、catch句内のジェネリックを許可しない可能性があります。



1
彼らがそれをより良く設計することができた唯一の方法は、10年以上の顧客のコードを非互換にすることでした。それは実行可能なビジネス上の決定でした。設計は正しかった... コンテキストが与えられた
スティーブンC

1
では、この例外をどのようにキャッチしますか?機能する唯一の方法は、生の型をキャッチすることEntityNotFoundExceptionです。しかし、それはジェネリックを役に立たなくするでしょう。
フランス、2018年

4

ジェネリックスはコンパイル時に型が正しいかどうかチェックされます。ジェネリック型情報は、型消去と呼ばれるプロセスで削除されます。たとえばList<Integer>、非ジェネリック型に変換されListます。

型の消去のため、型パラメーターは実行時に決定できません。

Throwableこのように拡張することが許可されているとしましょう:

public class GenericException<T> extends Throwable

次のコードを考えてみましょう:

try {
    throw new GenericException<Integer>();
}
catch(GenericException<Integer> e) {
    System.err.println("Integer");
}
catch(GenericException<String> e) {
    System.err.println("String");
}

以下のために消去を入力し、実行時には、実行するためにどのキャッチブロックを知ることができません。

したがって、ジェネリッククラスがThrowableの直接または間接のサブクラスである場合、コンパイル時エラーになります。

出典: 型消去に関する問題


ありがとう。これはTorstenによって提供さたものと同じ答えです。
Hosam Aly 2017年

いいえ、ちがいます。Torstenの回答は、タイプの消去/具体化が何であるかを説明していなかったので、助けにはなりませんでした。
おやすみオタクプライド

2

パラメータ化を保証する方法がないためだと思います。次のコードを検討してください。

try
{
    doSomethingThatCanThrow();
}
catch (MyException<Foo> e)
{
    // handle it
}

お気づきのように、パラメーター化は単なる構文上の砂糖です。ただし、コンパイラーは、パラメーター化がコンパイル範囲内のオブジェクトへのすべての参照にわたって一貫性を保つようにします。例外の場合、コンパイラーはMyExceptionが処理しているスコープからのみスローされることを保証する方法がありません。


はい、でも、キャストのように、なぜ「安全でない」とフラグが立てられないのですか?
eljenso、2009

キャストを使用すると、コンパイラに「この実行パスが期待される結果を生成することはわかっています」と言っているからです。例外を除いて、(すべての可能性のある例外について)「これがどこにスローされたのか知っています」とは言えません。しかし、上で言ったように、それは推測です。私はそこにいませんでした。
kdgregory 2009

「私はこの実行パスが期待される結果を生み出すことを知っています。」あなたは知らない、そう願う そのため、ジェネリックとダウンキャストは静的に安全ではありませんが、それでも許可されます。私はTorstenの答えに賛成しました。問題があるからです。ここでは私はしません。
eljenso

オブジェクトが特定のタイプであることを知らない場合は、キャストするべきではありません。キャストの全体的な考え方は、コンパイラーよりも多くの知識があり、その知識をコードの一部に明示的に含めることです。
kdgregory 2009

はい。MyExceptionからMyException <Foo>へのチェックされていない変換を実行したいので、コンパイラよりも多くの知識があるかもしれません。たぶん、あなたはそれがMyException <Foo>であることを「知っている」でしょう。
eljenso
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.