Java 8 lambda 函数接口的不明确方法 - 目标类型

我有以下代码:

public class LambdaTest1 {

    public static void method1(Predicate<Integer> predicate){
        System.out.println("Inside Predicate");
    }

    public static void method1(Function<Integer,String> function){
        System.out.println("Inside Function");
    }

    public static void main(String[] args) {        
        method1((i) -> "Test"); 
    }
}

这给了我一条错误消息,因为

“方法方法 1(谓词)对于 LambdaTest1 类型是不明确的”。

我可以看到,对于 函数接口,输入参数是 。但对于 ,返回类型为 。FunctionConsumerIntegerFunctionString

由于我的lambda调用的返回值为“Text” - 这应该调用我的函数接口而不是抛出此错误。Function

任何人都可以解释一下这种行为背后的原因吗?

还有另一个例子:

public class LambdaTest1 {

public static void method1(Consumer<Integer> consumer){
    System.out.println("Inside Consumer");
}

public static void method1(Predicate<Integer> predicate){
    System.out.println("Inside Predicate");
}

public static void main(String[] args) {

    List<Integer> lst = new ArrayList<Integer>();

    method1(i -> true); 

    method1(s -> lst.add(s)); //ambiguous error
}
}

同样在上面的代码中,该行给出了一个ambiguos错误,但上面的行工作正常。method1(s -> lst.add(s));method1(i -> true)


答案 1

正如本答案中所解释的,Java语言设计人员在选择重载方法与类型推断相结合的过程中做出了深思熟虑。因此,并非 lambda 表达式参数的每个方面都用于确定正确的重载方法。

最值得注意的是,在第一个示例中,lambda 表达式是一个隐式类型的 lambda 表达式,其返回值不考虑重载解析,而将其更改为,例如 将它转换为显式类型的 lambda 表达式,其返回值将被考虑。与 Java 语言规范 §15.12.2.2. 比较:(i) -> "Test"(Integer i) -> "Test"

阶段 1:识别通过严格调用适用的匹配 Arity 方法

参数表达式被认为与潜在适用方法的适用性相关,除非它具有以下形式之一:m

  • 隐式类型的 lambda 表达式 (§15.27.1)。

...

  • 一个显式类型的 lambda 表达式,其主体是与适用性无关的表达式。

  • 一个显式类型的 lambda 表达式,其主体是一个块,其中至少有一个结果表达式与适用性无关。

...

因此,显式类型的 lambda 表达式可以“与适用性相关”,具体取决于其内容,而隐式类型的表达式通常被排除在外。还有一个附录,更具体:

在解析目标类型之前,隐式类型的 lambda 表达式或不精确方法引用表达式的含义足够模糊,以至于包含这些表达式的参数被认为与适用性无关;它们被简单地忽略(除了它们的预期性),直到过载解决完成。

因此,使用隐式类型化无助于决定是否调用 or,并且由于两者都不更具体,因此在尝试推断 lambda 表达式的函数类型之前,方法选择会失败。(i) -> "Test"method1(Predicate<Integer>)method1(Function<Integer,String>)

另一种情况是,在 和 之间进行选择是不同的,因为一个方法的参数具有带有返回值的函数类型,而另一个方法具有非返回类型,这允许通过 lambda 表达式的形状选择适用的方法,这在链接的答案中已经讨论过。 仅与值兼容,因此不适合 .同样,只有 void 兼容,因此不适合 .method1(Consumer<Integer>)method1(Predicate<Integer>)voidvoidi -> trueConsumeri -> {}Predicate

只有少数情况下,形状不明确:

  • 当块从未正常完成时,例如 或arg -> { throw new Exception(); }
    arg -> { for(;;); }
  • 当 lambda 表达式具有形式并且也是语句时。此类表达式语句arg -> expressionexpression
    • 作业,例如arg -> foo=arg
    • 递增/递减表达式,例如arg -> counter++
    • 方法调用,如示例中所示s -> lst.add(s)
    • 实例化,例如arg -> new Foo(arg)

请注意,带括号的表达式不在此列表中,因此更改为
足以将其转换为不再与 void 兼容的表达式。同样,将它变成一个像这样的语句会阻止它与值兼容。因此,在这种情况下,很容易选择正确的方法。s -> lst.add(s)s -> (lst.add(s))s -> {lst.add(s);}


答案 2