具有通用抛出子句的 Lambda 和函数接口

2022-09-03 02:58:33

考虑以下 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;
}

调用编译时的 lambda 正常,而调用时的 lambda 不编译,而是给出以下编译错误:fg

Error:(10, 7) java: call() in <anonymous Generics$> cannot implement call() in CheckedCallable2
  overridden method does not throw java.lang.Exception

这是为什么呢?

在我看来,和 方法都是等价的:根据类型擦除的规则,它变得无界,并成为 ,因为这是类型上限。那么,为什么编译器认为重写的方法不会引发java.lang.Exception呢?CheckedCallable1.callCheckedCallable2.callVObjectEException

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

那么有人可以启发我为什么不允许这样做吗?

更新:

所以我发现了一些可能更有趣的东西。获取上述文件,将 每次出现的 to 更改为 ,并将 throws 子句添加到 中。编译作品!改回:编译中断!ExceptionIOExceptionmainException

这编译得很好:

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错误......


答案 1

我不认为有规则禁止这种模式。您很可能发现了编译器错误。

很容易证明这种模式不会导致不健全的代码,只需写下等效的内部类代码: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

此代码按预期运行和生成,无论我们使用哪个代码。只有我们必须捕获的例外情况不同。但如前所述,这是一个编译时工件。1interfacecall()


答案 2

推荐