Java lambda 返回 lambda

我试图在新的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

答案 1

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

这里,是方法调用的接收者,因此不考虑它的目标类型。l.stream(). map(n -> () -> { System.out.println(n); return null; })collect(Collectors.toList())List<Callable<Object>>

如果目标类型是已知的,则很容易证明嵌套的 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和泛型问世时,程序员不得不在表达式上重复类型参数,同时简单地将表达式包装成泛型方法,证明推断类型没有问题。直到Java 7,程序员才被允许省略这些不必要的类型参数重复(使用“菱形运算符”)。现在我们有一个类似的情况,将一个调用表达式包装成另一个方法,将接收器变成一个参数,证明这个限制是不必要的。因此,也许我们可以在Java 10中摆脱这种限制......new


答案 2

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

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