考虑这段java 8代码:
public class Generics {
public static <V, E extends Exception> V f(CheckedCallable1<V, E> callable) throws E {
return callable.call();
}
public static <V, E extends Exception> V g(CheckedCallable2<V, E> callable) throws E {
return callable.call();
}
public static void main(String[] args) {
f(() -> 1);
g(() -> 1);
}
}
interface Callable<V> {
V call() throws Exception;
}
interface CheckedCallable1<V, E extends Exception> {
V call() throws E;
}
interface CheckedCallable2<V, E extends Exception> extends Callable<V> {
@Override V call() throws E;
}
调用f
时的lambda编译正常,而调用g
时的lambda不编译,而是给出了这个编译错误:
Error:(10, 7) java: call() in <anonymous Generics$> cannot implement call() in CheckedCallable2
overridden method does not throw java.lang.Exception
为什么会这样?
在我看来,CheckedCallable1.call
和CheckedCallable2.call
方法都是等价的:根据类型擦除的规则,V
变为Object
,因为它是无界的,{ {1}}成为E
,因为它是上限类型。那么为什么编译器认为重写的方法不会抛出java.lang.Exception?
即使忽略类型擦除,这可能与此不相关,因为这一切都发生在编译时,但对我来说仍然没有意义:我不明白为什么会出现这种模式的原因(如果允许)比方说,不健全的java代码。
所以有人可以告诉我为什么不允许这样做?
更新
所以我发现了一些可能更有趣的东西。获取上述文件,将每个Exception
更改为Exception
,并将throws子句添加到IOException
。编译工作!改回main
:编译休息!
编译好:
Exception
此时它开始变得越来越像一个java bug ......
答案 0 :(得分:4)
我认为没有规定禁止这种模式。您很可能发现了编译器错误。
通过写下g(() -> 1);
的等效内部类代码,很容易证明这种模式不会导致代码不健全:
g(new CheckedCallable2<Integer, RuntimeException>() {
public Integer call() {
return 1;
}
});
编译并运行没有任何问题,即使在Java 6下(我假设它甚至可以在Java 5上运行但我没有JDK来测试它)并且没有理由为什么它在使用它时不应该工作一个lambda。在Netbeans中写下此代码甚至可以将其转换为lambda。
也没有运行时限制禁止这样的构造。除了事实上,没有强制执行异常规则并且所有内容都依赖于编译时检查,我们甚至可以证明,如果编译器通过手动创建编译器将创建的代码来接受我们的代码,它将起作用:
CheckedCallable2<Integer,RuntimeException> c;
try
{
MethodHandles.Lookup l = MethodHandles.lookup();
c=(CheckedCallable2)
LambdaMetafactory.metafactory(l, "call",
MethodType.methodType(CheckedCallable2.class),
MethodType.methodType(Object.class),
l.findStatic(Generics.class, "lambda$1", MethodType.methodType(int.class)),
MethodType.methodType(Integer.class)).getTarget().invokeExact();
} catch(Throwable t) { throw new AssertionError(t); }
int i=g(c);
System.out.println(i);
// verify that the inheritance is sound:
Callable<Integer> x=c;
try { System.out.println(x.call()); }// throws Exception
catch(Exception ex) { throw new AssertionError(ex); }
…
static int lambda$1() { return 1; }// the synthetic method for ()->1
此代码按预期运行并生成1
,无论我们使用哪interface
call()
。只有我们必须捕获的例外情况不同。但正如所说,这是一个编译时的工件。