web-dev-qa-db-ja.com

なぜJava Throwableの汎用サブクラスを許可しないのですか?

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

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

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

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

138
Hosam Aly

マークが言ったように、型は再定義できません。これは次の場合に問題になります。

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

両方 SomeException<Integer>およびSomeException<String>は同じ型に消去され、JVMが例外インスタンスを区別する方法がないため、どのcatchブロックを実行するべきかを判断する方法はありません。

148
Torsten Marek

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

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

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

14
IAdapter

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

この問題により、クリーンな抽象デザインが妨げられます。

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

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

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

12

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

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

次のように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");
}

type erasureにより、ランタイムはどのcatchブロックを実行するかを知りません。

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

出典:消去タイプの問題

4
outdev

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

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

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

2
kdgregory