为什么 Java 不支持泛型 Throwables?

发布于 2024-08-24 18:04:18 字数 633 浏览 9 评论 0原文

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

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

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

Why doesn't Java support generic Throwables?

I realize that type erasure complicates certain things, but obviously Java gets by with a lot already, so why not push it one more notch and allow generic Throwables, with comprehensive compile-time check for potential problems?


I feel like the type erasure argument is rather weak. Currently, we can't do:

void process(List<String> list) {
}

void process(List<Integer> list) {
}

Of course, we get by without it. I'm not asking that we should be able to do catch Bouncy<T1> and Bouncy<T2> in the same try block, but if we use them in disjoint contexts with strict compile-time enforceable rules (which is pretty much the way generics works right now), wouldn't it be workable?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

北城孤痞 2024-08-31 18:04:18

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

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

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

Java Language Specification
8.1.2 Generic Classes and Type Parameters:

This restriction is needed since the catch mechanism of the Java virtual machine works only with non-generic classes.

Personally, I think it's because we can't get any benefits of generics inside a catch clause. We can't write catch (Bouncy<String> ex) due to type erasure, but if we write catch (Bouncy ex), it would be useless to make it generic.

孤者何惧 2024-08-31 18:04:18

简短的回答:因为他们走了捷径,就像他们对擦除所做的那样。

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

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

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

Short answer: because they took shortcuts, just like they did with erasure.

Long answer: as others already indicated, because of erasure, there is no way to make a difference at runtime between a "catch MyException<String>" and "catch MyException<Integer>".

But that doesn't mean that there is no need for generic exceptions. I want generics to be able to use generic fields! They could have simply allowed generic exceptions, but only allow catching them in the raw state (e.g. "catch MyException").

Granted, this would make generics even more complicated.
This is to show just how bad the decision to erase generics was. When will we have a Java version that supports real generics (with RTTI), not the current syntactic sugar?

暖伴 2024-08-31 18:04:18

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

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

能做

catch( Mistake ea ) {
  ...
}

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

Type erasure. Runtime exception type has no generics information. Thus you cannot do

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

all you can do is

catch( Mistake ea ) {
  ...
}

And type erasure is how it was decided to preserve the backward compatibility when Java was moving from 1.4 to 1.5. Many people was unhappy then, and rightfully so. But having in mind the amount of deployed code, it was unthinkable to break code that worked happily in 1.4.

纸短情长 2024-08-31 18:04:18

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

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。这个论点似乎也攻击了泛型的使用,因为 JVM 不知道如何使用 List

List<T1> list;

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

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


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

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) {

You can still use generic methods, like this:

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

I do agree with Cristian's answer above. While the accepted answer is technically correct (insofar as it references the JVM specs), Cristian Vasile's answer is one that qualifies and even challenges the limitation.

There are least two arguments that I noted in answers to this question that I do not agree with and that I will rebut. If the arguments in these answers were correct, we could use these arguments to attack generics in other contexts where they are used successfully today.


The first argument states that we can't use this:

catch (Exception<T1> e) {}

because the JVM doesn't know how to work with Exception<T1>. This argument would seem to also attack this use of generics, on the basis that the JVM doesn't know how to use List<T1>:

List<T1> list;

The argument, of course, forgets that the compiler performs type erasure and so the JVM doesn't need to know how to handle Exception<T1>. It can simply handle Exception, just like it handles List.

Of course, we could never handle catch(Exception<T1> e) and catch(Exception<T2> e) in the same try/catch because of type erasure, but then again, that's no worse than with method arguments or return values today: we don't handle myMethod(List<T1>) and myMethod(List<T2>) today either... (I reiterate this aspect in the second rebuttal below.)


A second argument goes as follows. We don't allow this:

catch (Exception<T1> e) {}

because this wouldn't work:

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

OK, then why not disallow this:

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

because this does not work:

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

or this:

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

because this does not work:

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

In other words, why not disallow generics in most cases?

This second argument forgets that, although we couldn't possibly allow different generic constructs that that erase to the same non-generic construct in these contexts, we can still do the next best thing and allow generics as long as the types don't erase to the same type. That's what we do with method parameters: we allow you to use generics, but complain as soon as we detect duplicate signatures after type erasure. Well, we could have done about the same thing with exceptions and catch blocks...


In conclusion, I would expand on Cristian's answer. Instead of allowing generic exception classes and using the raw types in catch blocks:

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

Java could have gone the whole way without problems:

class MyException<T> {}
...
catch (MyException<Foo> e) {
淡淡绿茶香 2024-08-31 18:04:18

可以执行以下几项操作:

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

    <块引用>

    接口 Bouncy; {
       // ...
    <代码>}
    class BouncyString extends Exception 实现 Bouncy; {
       // ...
    }

  2. throws 子句可以引用类型参数,例如

    static无效
    throwIfInstanceOf(Throwable ex, Classclazz) 抛出 X {
       if (clazz.isInstance(ex)) throw clazz.cast(ex);
    <代码>}

Here are a couple of things you can do:

  1. Throwables can implement generic interfaces, as long as the throwable itself has no type parameters, e.g.

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

  2. A throws clause can refer to type parameters, e.g.

    static <X extends Throwable> void
    throwIfInstanceOf(Throwable ex, Class<X> clazz) throws X {
        if (clazz.isInstance(ex)) throw clazz.cast(ex);
    }

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文