为什么Java 8types推断不考虑Lambdas在重载select中抛出的exception?

我有一个关于lambda的Java 8推理及其相关的exception签名的问题。

如果我定义了一些方法foo:

public static <T> void foo(Supplier<T> supplier) { //some logic ... } 

那么我可以写出foo(() -> getTheT());简洁明了的语义foo(() -> getTheT()); 在大多数情况下对于给定的T 然而,在这个例子中,如果我的getTheT操作声明它throws Exception ,那么我的foo方法就不再编译供应商了:供应商get方法签名不会抛出exception。

看起来像一个体面的方式来解决这个问题将是重载foo接受任一选项,重载的定义是:

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

ThrowingSupplier被定义为

 public interface ThrowingSupplier<T> { public T get() throws Exception; } 

这样,我们有一个供应商types抛出exception,一个不是。 所需的语法是这样的:

 foo(() -> operationWhichDoesntThrow()); //Doesn't throw, handled by Supplier foo(() -> operationWhichThrows()); //Does throw, handled by ThrowingSupplier 

但是,由于lambdatypes不明确,导致问题(可能无法在供应商和投标供应商之间解决)。 foo((ThrowingSupplier)(() -> operationWhichThrows())); 会工作,但它摆脱了所需的语法的大部分简洁。

我想基本的问题是:如果Java编译器能够解决由于在供应商情况下抛出exception而导致我的某个lambdaexpression式不兼容的事实,为什么不能使用相同的信息来派生在次要的types推断情况下的lambda的types?

任何人都可以指点我的任何信息或资源也会被赞赏,因为我只是不太确定在哪里寻找更多的信息。

谢谢!

如果这让你感觉更好,在JSR-335devise过程中确实仔细考虑了这个话题。

问题不是“为什么不能”,而是“我们为什么不select”。 当我们find多个可能适用的重载时,我们当然可以select推测性地在每个签名集合下对lambda体进行属性化,并修剪那些lambda体未能进行types检查的候选项。

但是,我们认为这样做可能会造成更多的伤害, 这意味着,例如,在这个规则下,方法体的小的改变可能导致一些方法过载select的决定悄无声息地改变,而用户不打算这样做。 最后,我们得出的结论是,使用方法体中存在的错误来丢弃可能适用的候选项会导致更多的困惑而不是获益,特别是考虑到有一个简单和安全的解决方法 – 提供一个目标types。 我们认为这里的可靠性和可预测性超过了最佳的简洁性。

首先,你不必重载:D – 超载从来不是必需的; 使用2个不同的方法名,例如foofooX

其次,我不明白你为什么需要2种方法。 如果你想以不同的方式处理checked和uncheckedexception,可以在运行时完成。 要实现“exception透明”,你可以做

 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 

最后,可以实现消除lambda返回types的歧义。 编译器使用返回types,就像使用参数types来select最具体的方法一样。 这给了我们一个想法,就是将值与exceptiontypes一起包装,如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, eg 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"); } 

任何可以被接受为Supplier<T> lambda也可以被接受为Supplier<T> ThrowingSupplier<T> 。 以下编译:

 public static interface ThrowingSupplier<T>{ public T get() throws Exception; } public static <T> void foo(ThrowingSupplier<T> supplier) { } public static String getAString(){ return "Hello"; } public static String getAnotherString() throws Exception{ return "World"; } public static void main(String[] args) { foo(()->getAString()); foo(()->getAnotherString()); } 

鉴于上述情况,你可能不需要这个,但是如果foo 必须接受一个非抛出的Supplier<T> ,你总是可以把Exception抛出方法包装在一个方法中,该方法将其清理成一个未经检查的Exception:

 public static <T> void foo(Supplier<T> supplier) { } public static String getAString(){ return "Hello"; } public static String getAnotherString() throws Exception{ return "World"; } public static String getAnotherStringUnchecked(){ try{ return getAnotherString(); } catch(Exception e){ throw new RuntimeException("Error getting another string",e); } } public static void main(String[] args) throws Exception{ foo(()->getAString()); foo(()->getAnotherStringUnchecked()); }