为什么Java不支持通用Throwables?

pol*_*nts 23 java oop generics throwable

class Bouncy<T> extends Throwable {     
}
// Error: the generic class Bouncy<T> may not subclass java.lang.Throwable
Run Code Online (Sandbox Code Playgroud)

为什么Java不支持泛型Throwable

我意识到类型擦除使某些事情变得复杂,但显然Java已经经历了很多,所以为什么不再推动它,并允许泛型Throwables,通过全面的编译时检查潜在的问题?


我觉得类型擦除论证相当薄弱.目前,我们做不到:

void process(List<String> list) {
}

void process(List<Integer> list) {
}
Run Code Online (Sandbox Code Playgroud)

当然,我们没有它.我不要求,我们应该能够做到catch Bouncy<T1>,并Bouncy<T2>在同一个try块,但是如果我们使用他们在严格的编译时可执行的规则不相交的上下文(这是相当多的仿制药的工作方式现在),你说对不对可行吗?

axt*_*avt 17

Java语言规范 8.1.2通用类和类型参数:

由于Java虚拟机的catch机制仅适用于非泛型类,因此需要此限制.

就个人而言,我认为这是因为我们无法在一个catch条款中获得泛型的任何好处.catch (Bouncy<String> ex)由于类型擦除,我们无法写入,但如果我们编写catch (Bouncy ex),那么使它成为通用将是无用的.

  • 知道为什么会这样吗? (2认同)
  • 对不起,但是我无法解决JLS中的争论.就好像这个语句忘记了编译器执行类型擦除,在JVM使用其catch机制时有效地删除了泛型.那么,如果语法糖对运行时没有影响,那么为什么有必要避免使用语法糖,因为它会被删除?有了这个滑坡,为什么不禁止其他例子,如我自己的下面的那些?我的意思是,JVM在这些上下文中也不支持泛型,但它们在源代码中是允许的.:) (2认同)

Vla*_*hev 8

键入擦除.运行时异常类型没有泛型信息.因此你做不到

} catch( Mistake<Account> ea) {
  ...
} catch( Mistake<User> eu) {
...
}
Run Code Online (Sandbox Code Playgroud)

所能做的就是

catch( Mistake ea ) {
  ...
}
Run Code Online (Sandbox Code Playgroud)

类型擦除是当Java从1.4移动到1.5时决定保持向后兼容性的方式.许多人当时并不高兴,这是理所当然的.但是考虑到部署代码的数量,打破1.4中愉快工作的代码是不可想象的.


Cri*_*anu 8

简短回答:因为他们采取捷径,就像他们擦除一样.

答案很长:正如其他人已经指出的那样,由于擦除,在"catch MyException <String>"和"catch MyException <Integer>"之间无法在运行时产生差异.

但这并不意味着不需要通用异常.我希望泛型能够使用通用字段!他们可以简单地允许泛型异常,但只允许在原始状态下捕获它们(例如"catch MyException").

当然,这会使仿制药更加复杂.这是为了表明擦除泛型的决定有多糟糕.我们什么时候才能拥有支持真正的泛型(带RTTI)的Java版本,而不是当前的语法糖?


Mih*_*ila 5

您仍然可以使用通用方法,如下所示:

public class SomeException {
    private final Object target;

    public SomeException(Object target) {
        this.target = target;
    }

    public <T> T getTarget() {
        return (T) target;
    }
}

....

catch (SomeException e) {
    Integer target = e.getTarget();
}
Run Code Online (Sandbox Code Playgroud)

我确实同意克里斯蒂安上面的回答。虽然所接受的答案在技术上是正确的(就其引用 JVM 规范而言),但 Cristian Vasile 的答案符合甚至挑战了这一限制。

我在回答这个问题时指出,至少有两个论点我不同意,我将予以反驳。如果这些答案中的论点是正确的,我们可以使用这些论点在今天成功使用泛型的其他上下文中攻击泛型。


第一个参数指出我们不能使用它:

catch (Exception<T1> e) {}
Run Code Online (Sandbox Code Playgroud)

因为 JVM 不知道如何使用Exception<T1>. 这个论点似乎也攻击了泛型的使用,因为 JVM 不知道如何使用List<T1>

List<T1> list;
Run Code Online (Sandbox Code Playgroud)

当然,这个论点忘记了编译器执行类型擦除,因此 JVM 不需要知道如何处理Exception<T1>. 它可以简单地处理Exception,就像它处理一样List

当然,由于类型擦除,我们永远无法在同一个 try/catch 中处理catch(Exception<T1> e)and catch(Exception<T2> e),但话又说回来,这并不比今天的方法参数或返回值更糟糕:我们今天也不处理myMethod(List<T1>)and myMethod(List<T2>)......(我重申这一点下面第二个反驳的方面。)


第二个论点如下。我们不允许这样做:

catch (Exception<T1> e) {}
Run Code Online (Sandbox Code Playgroud)

因为这是行不通的:

catch (Exception<T1> e) {}
catch (Exception<T2> e) {}
Run Code Online (Sandbox Code Playgroud)

好吧,那为什么不禁止这个:

interface MyInterface {
    Comparable<Integer> getComparable();
}
Run Code Online (Sandbox Code Playgroud)

因为这不起作用

interface MyInterface {
    Comparable<Integer> getComparable();
    Comparable<String> getComparable();
}
Run Code Online (Sandbox Code Playgroud)

或这个:

interface MyInterface {
    void setComparable(Comparable<Integer> comparable);
}
Run Code Online (Sandbox Code Playgroud)

因为这不起作用

interface MyInterface {
    void setComparable(Comparable<Integer> comparable);
    void setComparable(Comparable<String> comparable);
}
Run Code Online (Sandbox Code Playgroud)

换句话说,为什么在大多数情况下不禁止泛型呢?

第二个参数忘记了,虽然我们不可能允许不同的泛型结构在这些上下文中擦除相同的非泛型结构,但我们仍然可以做下一个最好的事情并允许泛型,只要类型不擦除为同一类型。这就是我们对方法参数所做的事情:我们允许您使用泛型,但一旦我们在类型擦除后检测到重复签名,就会抱怨。好吧,我们可以用异常和 catch 块做同样的事情......


总之,我将扩展克里斯蒂安的答案。不要允许通用异常类在块中使用原始类型catch

class MyException<T> {}
...
catch (MyException e) { // raw
Run Code Online (Sandbox Code Playgroud)

Java 本来可以毫无问题地完成整个过程:

class MyException<T> {}
...
catch (MyException<Foo> e) {
Run Code Online (Sandbox Code Playgroud)