Lambda和具有泛型throw子句的函数接口

时间:2014-06-13 06:39:34

标签: java generics lambda java-8

考虑这段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.callCheckedCallable2.call方法都是等价的:根据类型擦除的规则,V变为Object,因为它是无界的,{ {1}}成为E,因为它是上限类型。那么为什么编译器认为重写的方法不会抛出java.lang.Exception?

即使忽略类型擦除,这可能与此不相关,因为这一切都发生在编译时,但对我来说仍然没有意义:我不明白为什么会出现这种模式的原因(如果允许)比方说,不健全的java代码。

所以有人可以告诉我为什么不允许这样做?

更新

所以我发现了一些可能更有趣的东西。获取上述文件,将每个Exception更改为Exception,并将throws子句添加到IOException。编译工作!改回main:编译休息!

编译好:

Exception

此时它开始变得越来越像一个java bug ......

1 个答案:

答案 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()。只有我们必须捕获的例外情况不同。但正如所说,这是一个编译时的工件。