为什么 Java 不支持泛型 Throwables?
题
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
块,但如果我们在具有严格的编译时可执行规则的不相交上下文中使用它们(这几乎是泛型现在的工作方式),那不是可行的吗?
解决方案
Java语言规范 8.1.2一般类和类型参数一>:
需要此限制,因为Java虚拟机的捕获机制只使用非通用类。
我个人认为,这是因为我们不能得到一个catch
子句中泛型的任何利益。我们不能写,由于类型擦除catch (Bouncy<String> ex)
,但如果我们写catch (Bouncy ex)
,这将是无用的,使其通用性。
其他提示
类型擦除。运行时异常类型没有泛型信息。因此,您不能做
} catch( Mistake<Account> ea) {
...
} catch( Mistake<User> eu) {
...
}
你的可以做是
catch( Mistake ea ) {
...
}
和类型擦除是它是如何决定保留时爪哇正从1.4〜1.5的向后兼容性。很多人不高兴的话,这是理所当然的。但是具有记部署的代码量,这是不可想象的打破,在1.4愉快地工作码。
简短的回答:因为他们把快捷方式,就像他们与擦除
龙答:正如其他人已经指出,因为擦除的,有没有办法让在运行时之间的差异“赶MyException <字符串>”和“赶MyException <整数>”
。但这并不意味着没有必要对通用例外即可。我想仿制药能够使用通用的领域!他们可以简单地使通用的例外,但只允许在原始状态,抓住他们(例如“抓MyException”)。
当然,这将使仿制药更加复杂。 这是显示擦除仿制药的决定是多么的糟糕。我们什么时候才能有一个真正的支持泛型(与RTTI)一个Java版本,而不是当前的语法糖?
您仍然可以使用通用方法,如下所示:
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
.
当然,我们永远无法处理 catch(Exception<T1> e)
和 catch(Exception<T2> e)
由于类型擦除,在同一个 try/catch 中,但话又说回来,这并不比今天的方法参数或返回值更糟糕:我们不处理 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) {
这里有几件事你 能 做:
- Throwables 可以实现通用接口,只要 throwable 本身没有类型参数,例如
interface Bouncy<E> {
// ...
}
class BouncyString extends Exception implements Bouncy<String> {
// ...
}
- A
throws
子句可以引用类型参数,例如static <X extends Throwable> void
throwIfInstanceOf(Throwable ex, Class<X> clazz) throws X {
if (clazz.isInstance(ex)) throw clazz.cast(ex);
}