用于功能接口的Java 8 lambda模糊方法 - 目标类型

时间:2016-09-02 14:15:05

标签: java lambda functional-programming java-8

我有以下代码:

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"); 
    }
}

这给我一条错误消息

  

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

我可以看到,对于FunctionConsumer功能界面,输入参数为Integer。但对于Function,返回类型为String

由于我的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
}
}

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

1 个答案:

答案 0 :(得分:9)

正如this answer中所解释的那样,Java语言设计者在选择与类型推断相结合的重载方法的过程中进行了深思熟虑。因此,并非lambda表达式参数的每个方面都用于确定正确的重载方法。

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

  

第1阶段:确定严格调用适用的匹配Arity方法

     

对于可能适用的方法m,参数表达式被视为与适用性相关,除非它具有以下形式之一:

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

...

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

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

  •   
     

...

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

  

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

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

另一种情况,在method1(Consumer<Integer>)method1(Predicate<Integer>)之间进行选择是不同的,因为一个方法的参数的函数类型的返回值为void,而另一个方法的参数为非void返回类型,允许通过lambda表达式的形状选择适用的方法,这已在linked answer中讨论过。 i -> true值兼容,因此不适合Consumer。同样,i -> {}只是 void-compatible ,因此不适合Predicate

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

  • 当块永远不会正常完成时,例如arg -> { throw new Exception(); }
    arg -> { for(;;); }
  • 当lambda表达式的格式为arg -> expression时,expression也是一个语句。这样的expression statements
    • 作业,例如arg -> foo=arg
    • 递增/递减表达式,例如arg -> counter++
    • 方法调用,如示例s -> lst.add(s)
    • 中所示
    • 实例化,例如arg -> new Foo(arg)

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