考虑下面这段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
,因为它是无界的,而E
变成了Exception
,因为这是类型的上限。那么,为什么编译器认为被覆盖的方法不会抛出java.lang.Exception呢?
即使忽略类型擦除,这在这里可能是不相关的,因为这都是在编译时发生的,这对我来说仍然没有意义:我看不出为什么这种模式,如果允许,会导致,比方说,不健全的java代码。
所以有人能告诉我为什么这是不允许的吗?
更新:
所以我发现了一些更有趣的东西。使用上面的文件,将每次出现的Exception
更改为IOException
,并将throws子句添加为main
。编译起作用了!改回Exception
:编译中断!
这段代码编译得很好:
import java.io.IOException;
public class Generics {
public static <V, E extends IOException> V f(CheckedCallable1<V, E> callable) throws E {
return callable.call();
}
public static <V, E extends IOException> V g(CheckedCallable2<V, E> callable) throws E {
return callable.call();
}
public static void main(String[] args) throws IOException {
f(() -> 1);
g(() -> 1);
}
}
interface Callable<V> {
V call() throws IOException;
}
interface CheckedCallable1<V, E extends IOException> {
V call() throws E;
}
interface CheckedCallable2<V, E extends IOException> extends Callable<V> {
@Override V call() throws E;
}
在这一点上,它开始看起来越来越像一个java bug……
发布于 2014-06-14 01:31:36
我不认为有一条规则禁止这种模式。很可能你发现了一个编译器错误。
只需写下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()
。只有我们必须捕获的异常才是不同的。但如上所述,这是一个编译时工件。
https://stackoverflow.com/questions/24199148
复制相似问题