Java lambda 返回一个 lambda

Posted

技术标签:

【中文标题】Java lambda 返回一个 lambda【英文标题】:Java lambda returning a lambda 【发布时间】:2015-01-08 11:17:06 【问题描述】:

我正在尝试在新的 JDK 8 函数式编程领域中做一些看似相对基本的事情,但我无法让它发挥作用。我有这个工作代码:

import java.util.*;
import java.util.concurrent.*;
import java.util.stream.*;

public class so1 
   public static void main() 
      List<Number> l = new ArrayList<>(Arrays.asList(1, 2, 3));
      List<Callable<Object>> checks = l.stream().
               map(n -> (Callable<Object>) () -> 
                  System.out.println(n);
                  return null;
               ).
               collect(Collectors.toList());
   

它需要一个数字列表并生成一个可以打印出来的函数列表。但是,显式转换为 Callable 似乎是多余的。在我和IntelliJ 看来。我们都同意这也应该有效:

List<Callable<Object>> checks = l.stream().
       map(n -> () -> 
          System.out.println(n);
          return null;
       ).
       collect(Collectors.toList());

但是我得到一个错误:

so1.java:10: error: incompatible types: cannot infer type-variable(s) R
      List<Callable<Object>> checks = l.stream().map(n -> () -> System.out.println(n); return null;).collect(Collectors.toList());
                                                    ^
    (argument mismatch; bad return type in lambda expression
      Object is not a functional interface)
  where R,T are type-variables:
    R extends Object declared in method <R>map(Function<? super T,? extends R>)
    T extends Object declared in interface Stream
1 error

【问题讨论】:

而不是强制转换,更喜欢map(..) 的显式类型参数。 l.stream().&lt;Callable&lt;Object&gt;&gt; map(...) 相关:***.com/questions/24794924/… 【参考方案1】:

您遇到了 Java 8 目标类型的限制,该限制适用于方法调用的接收者。虽然目标类型(大多数情况下)适用于参数类型,但它不适用于调用方法的对象或表达式。

这里l.stream(). map(n -> () -> System.out.println(n); return null; )collect(Collectors.toList())方法调用的接收者,所以不考虑目标类型List&lt;Callable&lt;Object&gt;&gt;

如果目标类型已知,则很容易证明嵌套的 lambda 表达式有效,例如

static <T> Function<T,Callable<Object>> toCallable() 
    return n -> () -> 
        System.out.println(n); 
        return null;
    ;

工作没有问题,你可以用它来解决你原来的问题

List<Callable<Object>> checks = l.stream()
    .map(toCallable()).collect(Collectors.toList());

你也可以通过引入一个辅助方法来解决这个问题,它将第一个表达式的角色从方法接收器更改为参数

// turns the Stream s from receiver to a parameter
static <T, R, A> R collect(Stream<T> s, Collector<? super T, A, R> collector) 
    return s.collect(collector);

并将原来的表达式改写为

List<Callable<Object>> checks = collect(l.stream().map(
    n -> () -> 
        System.out.println(n); 
        return null;
    ), Collectors.toList());

这不会降低代码的复杂性,但可以毫无问题地编译。对我来说,这是一种似曾相识的感觉。当 Java 5 和泛型问世时,程序员不得不在 new 表达式上重复类型参数,同时简单地将表达式包装到泛型方法中证明推断类型没有问题。直到 Java 7 才允许程序员省略这些不必要的类型参数重复(使用“菱形运算符”)。现在我们也有类似的情况,把一个调用表达式包装成另一个方法,把receiver变成一个参数,证明这个限制是不必要的。所以也许我们可以摆脱 Java 10 中的这个限制……

【讨论】:

我可能需要几个星期才能理解这一点,但我想我会接受 ;)【参考方案2】:

我遇到了同样的问题,并且能够通过将泛型类型参数显式指定为 map 来解决它,如下所示:

List<Callable<Object>> checks = l.stream().
   <Callable<Object>>map(n -> () -> 
      System.out.println(n); 
      return null;
   ).
   collect(Collectors.toList());

【讨论】:

【参考方案3】:

我还没有深入研究类型推断如何与 lambda 配合使用的确切规则。但是,从通用语言设计的角度来看,编写语言规则并不总是可以让编译器找出我们认为应该做的所有事情。我一直是 Ada 语言编译器的编译器维护者,我熟悉那里的许多语言设计问题。 Ada 在很多情况下都使用类型推断(如果不查看包含该构造的整个表达式就无法确定构造的类型,我认为这个 Java lambda 表达式也是如此)。当理论上只有一种可能的解释时,有些语言规则会导致编译器将某些表达式拒绝为模棱两可。一个原因,如果我没记错的话,是有人发现了这样一种情况,即让编译器找出正确解释的规则需要编译器通过一个表达式进行 17 次传递才能正确解释它。

因此,虽然我们可能认为编译器“应该”能够在特定情况下解决某些问题,但它可能只是不可行。

【讨论】:

【参考方案4】:

首先你必须知道编译器是如何得到一个 lambda 表达式的类型的。它是通过目标类型来实现的,这意味着您将 lambda 表达式分配给的变量的类型。在你的情况下,如果你

Function<Integer, Callable<Object>> fn = n -> () ->  System.out.println(n); return null; 

这是 lambda 获取其类型的方式:Function&lt;Integer, Callable&lt;Object&gt;&gt;

那你得看看泛型类型中的类型推断: map 的返回类型是&lt;R&gt; Stream&lt;R&gt;,R 将由您传递给函数的参数的类型决定。如果你map(x-&gt;"some string"),那么结果就是Stream&lt;String&gt;。现在这就是问题所在,R 的类型是 lambda 的类型。但是 lambda 需要一个目标类型,即变量 R。

工作代码之所以有效,是因为它将 lambda 显式转换为一个类型。

【讨论】:

以上是关于Java lambda 返回一个 lambda的主要内容,如果未能解决你的问题,请参考以下文章

将 Lambda 应用于返回另一个列表的列表的 Java 8 惯用方法?

Java中的lambda返回值

Java8--Lambda表达式

Java语法—— Lambda表达式

java#lambda相关之方法引用

如何从 JAVA 调用 AWS lambda 函数?