24
class Bouncy<T> extends Throwable {     
}
// Error: the generic class Bouncy<T> may not subclass java.lang.Throwable

为什么Java不支持泛型Throwable

我意识到类型擦除会使某些事情复杂化,但显然 Java 已经做了很多事情,所以为什么不把它再推一个档次并允许泛型Throwable,对潜在问题进行全面的编译时检查呢?


我觉得类型擦除参数相当弱。目前,我们不能这样做:

void process(List<String> list) {
}

void process(List<Integer> list) {
}

当然,没有它我们也能过得去。我不是要求我们应该能够在同一个块中做catch Bouncy<T1>,但是如果我们在具有严格编译时可执行规则的不相交的上下文中使用它们(这几乎是泛型现在的工作方式),不是吗?可行吗?Bouncy<T2>try

4

5 回答 5

18

Java 语言规范 8.1.2 泛型类和类型参数

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

就个人而言,我认为这是因为我们无法在catch子句中获得泛型的任何好处。catch (Bouncy<String> ex)由于类型擦除,我们无法编写,但如果我们编写catch (Bouncy ex),使其泛型将毫无用处。

于 2010-03-15T02:00:10.120 回答
9

简短的回答:因为他们走捷径,就像他们用擦除一样。

长答案:正如其他人已经指出的那样,由于擦除,在运行时无法区分“catch MyException<String>”和“catch MyException<Integer>”。

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

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

于 2012-01-17T08:09:13.290 回答
8

类型擦除。运行时异常类型没有泛型信息。因此你不能做

} catch( Mistake<Account> ea) {
  ...
} catch( Mistake<User> eu) {
...
}

能做的就是

catch( Mistake ea ) {
  ...
}

类型擦除是 Java 从 1.4 迁移到 1.5 时决定保留向后兼容性的方式。许多人当时不高兴,这是理所当然的。但是考虑到部署代码的数量,破坏在 1.4 中运行良好的代码是不可想象的。

于 2010-03-15T02:27:43.310 回答
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();
}

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

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


第一个论点表明我们不能使用它:

catch (Exception<T1> e) {}

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

List<T1> list;

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

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


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

catch (Exception<T1> e) {}

因为这行不通:

catch (Exception<T1> e) {}
catch (Exception<T2> e) {}

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

interface MyInterface {
    Comparable<Integer> getComparable();
}

因为这不起作用

interface MyInterface {
    Comparable<Integer> getComparable();
    Comparable<String> getComparable();
}

或这个:

interface MyInterface {
    void setComparable(Comparable<Integer> comparable);
}

因为这不起作用

interface MyInterface {
    void setComparable(Comparable<Integer> comparable);
    void setComparable(Comparable<String> comparable);
}

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

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


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

class MyException<T> {}
...
catch (MyException e) { // raw

Java 本可以毫无问题地一路走好:

class MyException<T> {}
...
catch (MyException<Foo> e) {
于 2012-08-15T20:59:21.843 回答
3

以下是您可以做的几件事:

  1. Throwables 可以实现泛型接口,只要 throwable 本身没有类型参数,例如

    interface Bouncy<E> {
        // ...
    }
    class BouncyString extends Exception implements Bouncy<String> {
        // ...
    }

  2. throws子句可以引用类型参数,例如
    static <X extends Throwable> void
    throwIfInstanceOf(Throwable ex, Class<X> clazz) throws X {
        if (clazz.isInstance(ex)) throw clazz.cast(ex);
    }

于 2011-05-21T14:28:49.873 回答