为什么Java不支持通用Throwables?

时间:2010-03-15 01:53:02

标签: java generics oop throwable

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

为什么Java不支持通用Throwable

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


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

void process(List<String> list) {
}

void process(List<Integer> list) {
}

当然,我们没有它。我不是要求我们能够在同一个catch Bouncy<T1>块中执行Bouncy<T2>try,但是如果我们在具有严格编译时可执行规则的不相关上下文中使用它们(几乎就是仿制药现在的工作方式),它不可行吗?

5 个答案:

答案 0 :(得分:17)

Java语言规范 8.1.2 Generic Classes and Type Parameters

  

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

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

答案 1 :(得分:8)

键入擦除。运行时异常类型没有泛型信息。因此,您无法做到

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

可以做的所有

catch( Mistake ea ) {
  ...
}

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

答案 2 :(得分:8)

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

答案很长:正如其他人已经指出的那样,由于擦除,在“catch MyException&lt; String&gt;”之间无法在运行时产生差异。并且“捕捉MyException&lt; Integer&gt;”。

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

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

答案 3 :(得分:4)

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

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);
}

换句话说,为什么不在大多数情况下禁止仿制药?

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


总之,我会扩展Cristian的答案。而不是使用catch块中的原始类型允许通用异常类

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

Java本可以完全没有问题:

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

答案 4 :(得分:2)

以下是可以做的一些事情:

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

      

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

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