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

Java 具有泛型throw子句的lambda和函数接口,java,generics,lambda,java-8,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&g

考虑以下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;
}
为什么会这样

在我看来,
CheckedCallable1.call
CheckedCallable2.call
方法都是等价的:根据类型擦除规则,
V
变成了
对象,因为它是无界的,而
E
变成了
异常,因为这是上限类型。那么为什么编译器认为被重写的方法不会抛出java.lang.Exception呢

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

有人能告诉我为什么这是不允许的吗

更新:

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

这很好:

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;
}
import java.io.IOException;
公共类泛型{
公共静态vf(CheckedCallable1 callable)抛出E{
返回callable.call();
}
公共静态VG(CheckedCallable2 callable)抛出E{
返回callable.call();
}
公共静态void main(字符串[]args)引发IOException{
f(()->1);
g(()->1);
}
}
接口可调用{
V call()抛出IOException;
}
接口CheckedCallable1{
V call()抛出E;
}
接口CheckedCallable2扩展了Callable{
@覆盖V调用()抛出E;
}

在这一点上,它开始越来越像java bug…

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

通过写下
g(()->1)的等价内部类代码,很容易证明这种模式不会导致不健全的代码

g(新的CheckedCallable2(){
公共整数调用(){
返回1;
}
});
即使在Java6下,它的编译和运行也没有任何问题(我假设它甚至可以在Java5上运行,但我没有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
checkedCallable2c;
尝试
{
MethodHandles.Lookup l=MethodHandles.Lookup();
c=(CheckedCallable2)
元工厂(l,“调用”,
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){抛出新断言错误(t);}
int i=g(c);
系统输出打印LN(i);
//验证继承是否正确:
可调用x=c;
请尝试{System.out.println(x.call());}//引发异常
catch(Exception ex){throw new AssertionError(ex);}
…
static int lambda$1(){return 1;}//的合成方法()->1

此代码按预期运行并生成
1
,无论我们使用哪个
接口
调用()
。只有我们必须抓住的例外情况不同。但正如所说,这是一个编译时工件。

+1也是为了让我意识到java 8中的通用异常类型支持,这个问题最终促使我让java 8在我的PC上工作。我刚刚安装了KeplerR2、jdk1.8.0\u 05。。。我在同一个位置得到了一个运行时异常,而不是编译时异常:在Generics.main(Generics.java:10)处,由以下原因引起:java.lang.ClassFormatError:类文件Generics$$Lambda$2中的重复方法名和签名给出了什么?有趣的是,Eclipse的编译器没有发出错误。不知道为什么。当遇到非常奇怪的错误时,我还不相信Eclipse编译器。尝试与其他IDE或javac编译器进行双重检查。Eclipse对Java8的支持在EdgeCase中似乎有很多缺陷。这在Java缺陷跟踪器中得到了体现:很酷,谢谢您尝试!我将向甲骨文提交一个bug。
g(new CheckedCallable2<Integer, RuntimeException>() {
    public Integer call() {
        return 1;
    }
});
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