为什么Java 8类型推断不考虑Lambdas在重载选择中引发的异常?

pau*_*aul 40 java lambda type-inference exception java-8

关于lambdas及其相关的异常签名,我有一个关于Java 8推断的问题.

如果我定义一些方法foo:

public static <T> void foo(Supplier<T> supplier) {
    //some logic
    ...
}
Run Code Online (Sandbox Code Playgroud)

然后,我得到了foo(() -> getTheT());在大多数情况下能够为给定的内容编写的漂亮而简洁的语义T.但是,在此示例中,如果我的getTheT操作声明了它throws Exception,我的foo方法使得供应商不再编译:供应商方法签名get不会抛出异常.

这似乎是解决这个问题的一个不错的方法是重载foo以接受任一选项,重载定义为:

public static <T> void foo(ThrowingSupplier<T> supplier) {
   //same logic as other one
   ...
}
Run Code Online (Sandbox Code Playgroud)

其中ThrowingSupplier定义为

public interface ThrowingSupplier<T> {
   public T get() throws Exception;
}
Run Code Online (Sandbox Code Playgroud)

通过这种方式,我们有一个引发异常的供应商类型和一个不引发异常的供应商类型.所需的语法将是这样的:

foo(() -> operationWhichDoesntThrow()); //Doesn't throw, handled by Supplier
foo(() -> operationWhichThrows()); //Does throw, handled by ThrowingSupplier
Run Code Online (Sandbox Code Playgroud)

但是,这会导致问题,因为lambda类型不明确(可能无法在Supplier和ThrowingSupplier之间解决).做一个明确的演员foo((ThrowingSupplier)(() -> operationWhichThrows()));可以工作,但它摆脱了所需语法的大部分简洁性.

我想基本的问题是:如果Java编译器能够解决我的一个lambdas由于它在仅供应商案例中抛出异常而不兼容的事实,为什么它不能使用相同的信息来导出二级,类型推理案例中lambda的类型?

任何人都可以指出的任何信息或资源同样值得赞赏,因为我不太确定在哪里可以找到有关此事的更多信息.

谢谢!

Bri*_*etz 30

如果它让您感觉更好,那么在JSR-335设计过程中确实会仔细考虑这个主题.

问题不是"为什么不能",而是"我们为什么选择不这样做".当我们发现多个可能适用的重载时,我们当然可以选择在每组签名下推测性地归属lambda主体,并修剪lambda主体未能进行类型检查的候选者.

但是,我们的结论是,这样做可能弊大于利; 例如,这意味着,根据此规则,对方法主体的微小更改可能会导致某些方法过载选择决策在没有用户打算这样做的情况下进行静默更改.最后,我们得出结论,使用方法体中存在的错误来丢弃可能适用的候选者会导致更多的混淆而不是利益,特别是考虑到有一个简单而安全的解决方法 - 提供目标类型.我们认为这里的可靠性和可预测性超过了最佳的简洁性.


Zho*_*gYu 5

首先,你不必过载:D - 重载永远不是必需的; 使用2个不同的方法名称,例如foofooX

其次,我不明白你为什么需要2种方法.如果要以不同方式处理已检查和未检查的异常,可以在运行时完成.要实现"异常透明度",您可以这样做

interface SupplierX<T, X extends Throwable>
{
    T get() throws X;
}

<T, X extends Throwable> void foo(Supplier<T, X> supplier)  throws X { .. }


foo( ()->"" );  // throws RuntimeException

foo( ()->{ throw new IOException(); } );  // X=IOException
Run Code Online (Sandbox Code Playgroud)

最后,可以实现抛弃lambda返回类型的歧义; 编译器使用返回类型,就像使用参数类型选择最具体的方法一样.这使我们有了将值与异常类型一起包装的想法,Result<T,X>就像他们所说的"monad"一样.

interface Result<T, X extends Throwable>
{
    T get() throws X;
}

// error type embedded in return type, not in `throws` clause

static Result<String,        Exception> m1(){ return ()->{ throw new Exception();};  }
static Result<String, RuntimeException> m2(){ return ()->{ return "str";         };  }

  // better to have some factory method, e.g. return Result.success("str");

public static void main(String[] args)
{
    foo(()->m1());  // foo#2 is not applicable
    foo(()->m2());  // both applicable; foo#2 is more specific
}



interface S1<T> { T get(); }  

static <T> void foo(S1<Result<T, ? extends        Exception>> s)
{
    System.out.println("s1");}
}


interface S2<T> { T get(); }  // can't have two foo(S1) due to erasure

static <T> void foo(S2<Result<T, ? extends RuntimeException>> s)
{
    System.out.println("s2");
}
Run Code Online (Sandbox Code Playgroud)

  • 看看[Comparator](https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html)中的方法 - 在java8的早期,它们被重载了; 但他们后来简化了语言规范,而且重载不再有效,所以我们有奇怪的东西,比如`比较`,`比较',`比较龙`等. (2认同)