在Java中,定义通用异常类是非法的.编译器将拒绝编译以下内容:
public class Foo<T> extends Throwable {
// whatever...
}
Run Code Online (Sandbox Code Playgroud)
但是,这个Scala代码编译得很好:
class Foo[T](val foo: T) extends Throwable
Run Code Online (Sandbox Code Playgroud)
即使是奇怪的,只要我捕获原始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());
}
}
}
Run Code Online (Sandbox Code Playgroud)
这编译,运行和打印"测试".
这是根据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
}
Run Code Online (Sandbox Code Playgroud)
简短回答:
答案很长:
Java语言规范说(第8.1.2节)关于声明这样一个类:
如果泛型类是Throwable(第11.1.1节)的直接或间接子类,则是编译时错误.
由于Java虚拟机的catch机制仅适用于非泛型类,因此需要此限制.
它说关于抛出异常(§14.18):
throw语句中的表达式必须表示
1)引用类型的变量或值,可赋值(§5.2)类型为Throwable,或
2)空引用,或发生编译时错误.
Expression的引用类型将始终是一个类类型(因为没有接口类型可分配给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
}
Run Code Online (Sandbox Code Playgroud)
实现异常捕获的方式是有一个异常表,它指定要监视的字节码范围和要捕获的关联类.该类不能具有类型参数,因为无法在字节码中描述它们(JVM规范,§2.10和§3.12).
由于类型擦除,throw子句只引用Foo,并且这两个catch方法将成为异常表中的两个引用该类的条目,Foo无论如何这都是无用的和不可能的.因此,Java语言中无法使用语法,只能捕获语法Foo.
因此,能够声明参数化异常变得非常无用且具有潜在危险性.因此,即使JVM本身并不关心,他们也完全被语言所禁止.
| 归档时间: |
|
| 查看次数: |
916 次 |
| 最近记录: |