Java 为什么我可以在Scala中定义泛型异常类型?
在Java中,定义泛型异常类是非法的。编译器将拒绝编译以下内容:Java 为什么我可以在Scala中定义泛型异常类型?,java,scala,jvm,Java,Scala,Jvm,在Java中,定义泛型异常类是非法的。编译器将拒绝编译以下内容: public class Foo<T> extends Throwable { // whatever... } 更奇怪的是,只要捕获原始Foo类型,我就可以在Java代码中使用这个Scala类: public class Main { public static void main(String[] args) { try { throw new Foo<
public class Foo<T> extends Throwable {
// whatever...
}
更奇怪的是,只要捕获原始Foo
类型,我就可以在Java代码中使用这个Scala类:
public class Main {
public static void main(String[] args) {
try {
throw new Foo<String>("test");
}
catch(Foo e) {
System.out.println(e.foo());
}
}
}
公共类主{
公共静态void main(字符串[]args){
试一试{
抛出新Foo(“测试”);
}
捕获(Foo-e){
System.out.println(e.foo());
}
}
}
这将编译、运行和打印“测试”
这是根据JLS和JVM规范定义的,还是只是碰巧工作
Java对泛型异常的限制是纯粹的语言限制,还是同样适用于字节码(在这种情况下,Scala编译器生成的字节码将无效)
编辑:这是Scala类在反编译后的工作:
public class Foo<T> extends java.lang.Throwable {
public T value();
Code:
0: aload_0
1: getfield #15 // Field value:Ljava/lang/Object;
4: areturn
public Foo(T);
Code:
0: aload_0
1: aload_1
2: putfield #15 // Field value:Ljava/lang/Object;
5: aload_0
6: invokespecial #22 // Method java/lang/Throwable."<init>":()V
9: return
}
公共类Foo扩展了java.lang.Throwable{
公共价值观();
代码:
0:aload_0
1:getfield#15//字段值:Ljava/lang/Object;
4:轮到你了
公共食品(T);
代码:
0:aload_0
1:aload_1
2:putfield#15//字段值:Ljava/lang/Object;
5:aload_0
6:invokespecial#22//方法java/lang/Throwable。“:()V
9:返回
}
以下是我们在讨论中得出的答案
这是根据JLS和JVM规范定义的,还是仅仅根据JLS和JVM规范定义的
偶然工作
它不必根据Java语言规范进行很好的定义,因为它是一种不同的语言
JVM OTOH在抛出和捕获此类异常方面没有问题,因为由于类型擦除,它对字节码没有影响
然而,有趣的问题仍然存在,为什么Java首先禁止使用泛型类扩展Throwable。简短回答:
- JVM规范禁止抛出和捕获参数化异常,但不关心声明。它甚至没有禁止,只是没有办法用字节码来表示类型参数,所以这个问题是没有意义的
- JLS禁止声明它们,因为您无论如何都无法使用它们
长答覆: Java语言规范(§8.1.2)说明了如何声明此类: 如果泛型类是Throwable(§11.1.1)的直接或间接子类,则为编译时错误 由于Java虚拟机的捕获机制仅适用于非泛型类,因此需要此限制 它说的是关于抛出异常(§14.18): throw语句中的表达式必须表示 1) 可分配(§5.2)给可丢弃类型的参考类型的变量或值,或 2) 出现空引用或编译时错误 表达式的引用类型将始终是未参数化的类类型(因为没有接口类型可分配给Throwable)(因为Throwable的子类不能是泛型的(§8.1.2)) 当泛型被添加到Java语言时,这个限制被添加,因为它们没有添加到JVM本身:JVM上只存在原始类型 在定义类的地方仍然有关于类型参数的信息,但在使用的地方却没有!与参数化异常的声明无关,这在JVM级别上是不可能的:
try {
throw new Foo<String>("test");
} catch(Foo<Int> e) {
// int
} catch(Foo<String> e) {
// string
}
试试看{
抛出新Foo(“测试”);
}捕获(Foo-e){
//int
}捕获(Foo-e){
//串
}
异常捕获的实现方式是有一个异常表,指定要监视的字节码范围和要捕获的关联类。该类不能有类型参数,因为无法用字节码描述它们(JVM规范,§2.10和§3.12)
由于类型擦除,throw子句只引用Foo
,而这两个catch方法将成为异常表中的两个条目,它们都引用类Foo
,这既无用又不可能。因此,Java语言中不可能使用语法,只能捕获Foo
因此,能够声明参数化的异常变得非常无用,并且具有潜在的危险。因此在语言中它们被完全禁止,即使JVM本身并不在意。显然,Scala生成的字节码是有效的,否则任何Scala程序都无法运行。我认为这并不明显。即使在Scala中,通常也不定义泛型异常类型。即使你这样做了,仅仅因为它在主流JVM上工作并不意味着它根据规范是有效的。你考虑过类型擦除吗?无论抛出泛型还是非.generic类型的对象,字节码都不会有任何区别。就JVM而言,泛型类型根本不存在。字节码中仍然有一些信息表明这是一个泛型类(请参阅我的编辑)。我看不到抛出。我声称:当你抛出时,字节码不会有什么不同。您提到的额外信息只是为了支持反射工具,如IDE、debugger或javap,它们对JVM没有任何作用。我猜“如果泛型类是Throwable的直接或间接子类,这是一个编译时错误”只适用于类的定义,而不适用于
throw new Foo(“test”)中的使用代码>?正确,但它错误地认为,正因为如此,抛出新的Foo(“测试”)
无论如何都不会发生,请参见编辑。
try {
throw new Foo<String>("test");
} catch(Foo<Int> e) {
// int
} catch(Foo<String> e) {
// string
}