为什么Java不允许Throwable的泛型子类?

Hos*_*Aly 142 java generics language-design exception

根据Java语言规范,第3版:

如果泛型类是其直接或间接子类,则为编译时错误Throwable.

我希望理解为什么做出这个决定.通用异常有什么问题?

(据我所知,泛型只是编译时的语法糖,他们将被翻译成Object反正在.class文件,因此有效地声明一个泛型类是仿佛一切都在它是一个Object,请纠正我,如果我错了.)

Tor*_*rek 149

正如mark所说,这些类型是不可恢复的,这在以下情况下是一个问题:

try {
   doSomeStuff();
} catch (SomeException<Integer> e) {
   // ignore that
} catch (SomeException<String> e) {
   crashAndBurn()
}
Run Code Online (Sandbox Code Playgroud)

SomeException<Integer>SomeException<String>被擦除至相同类型的,也没有办法为JVM区分异常情况,因此没有办法知道哪些catch应该被执行的块.

  • 因此规则不应该是"泛型类型不能子类Throwable",而是"catch子句必须始终使用原始类型". (52认同)
  • @ SuperJedi224 - 没有.它确实正确 - *考虑到仿制药**必须向后兼容的约束.* (4认同)
  • 但"可再生"意味着什么? (3认同)
  • 他们可能只是禁止使用两个相同类型的捕获块.因此单独使用SomeExc <Integer>是合法的,只使用SomeExc <Integer>和SomeExc <String>一起是非法的.这不会有任何问题,或者是吗? (3认同)
  • 哦,现在我明白了.我的解决方案会导致RuntimeExceptions出现问题,而不必声明.因此,如果SomeExc是RuntimeException的子类,我可以抛出并显式捕获SomeExc <Integer>,但也许其他一些函数默默地抛出SomeExc <String>,而SomeExc <Integer>的catch块也会意外地捕获它. (3认同)

IAd*_*ter 14

以下是如何使用异常的简单示例:

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

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; }
}
Run Code Online (Sandbox Code Playgroud)

尝试编译上述报告错误:

% 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
Run Code Online (Sandbox Code Playgroud)

这种限制是明智的,因为几乎任何捕获此类异常的尝试都必须失败,因为该类型不可恢复.有人可能会认为异常的典型用法如下:

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;
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

这是不允许的,因为catch子句中的类型不可恢复.在撰写本文时,Sun编译器在这种情况下报告了一连串的语法错误:

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

由于异常不能参数化,因此语法受到限制,因此必须将类型写为标识符,而不包含以下参数.

  • 当你说'可兑换'时你是什么意思?'可再生'不是一个字. (2认同)
  • 我自己不知道这个词,但在谷歌中快速搜索得到了这个:http://java.sun.com/docs/books/jls/third_edition/html/typesValues.html#4.7 (2认同)

小智 13

这主要是因为它的设计方式很糟糕.

此问题可防止干净的抽象设计,例如

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

    E getById(ID id) throws EntityNotFoundException<E, ID>;
}
Run Code Online (Sandbox Code Playgroud)

一个catch子句因泛型而失败的事实并没有被证实是没有任何借口的.编译器可以简单地禁止在catch子句中扩展Throwable或禁止泛型的具体泛型类型.

  • 他们可以更好地设计它的唯一方法是使大约 10 年的客户代码不兼容。这是一个可行的商业决策。设计是正确的......**考虑到上下文**。 (2认同)

out*_*dev 6

在编译时检查泛型的类型正确性。然后在称为类型擦除的过程中删除通用类型信息。例如,List<Integer>将转换为非泛型类型List

由于类型擦除,无法在运行时确定类型参数。

假设您可以Throwable像这样扩展:

public class GenericException<T> extends Throwable
Run Code Online (Sandbox Code Playgroud)

现在让我们考虑以下代码:

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

由于类型擦除,运行时将不知道要执行哪个 catch 块。

因此,如果泛型类是 Throwable 的直接或间接子类,则会出现编译时错误。

来源: 类型擦除问题

  • 不,这不对。托斯顿的回答对我没有帮助,因为它没有解释什么是类型擦除/具体化。 (2认同)