java lambda返回一个lambda

MK.*_*MK. 21 java lambda functional-programming java-8

我试图在功能编程的新jdk8版本中做一些看起来相对基本的东西,但是无法让它工作.我有这个工作代码:

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());
   }
}
Run Code Online (Sandbox Code Playgroud)

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

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

但是我收到一个错误:

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
Run Code Online (Sandbox Code Playgroud)

Hol*_*ger 24

您遇到了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;
    };
}
Run Code Online (Sandbox Code Playgroud)

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

List<Callable<Object>> checks = l.stream()
    .map(toCallable()).collect(Collectors.toList());
Run Code Online (Sandbox Code Playgroud)

您还可以通过引入一个帮助方法来解决问题,该方法将第一个表达式从方法接收器的角色更改为参数

// 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);
}
Run Code Online (Sandbox Code Playgroud)

并将原始表达式重写为

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

这不会降低代码的复杂性,但可以毫无问题地进行编译.对我来说,这是一个似曾相识.当Java 5和Generics出现时,程序员必须在new表达式上重复类型参数,而简单地将表达式包装到泛型方法中证明推断类型是没有问题的.直到Java 7才允许程序员省略这些不必要的类型参数重复(使用"菱形运算符").现在我们有类似的情况,将调用表达式包装到另一个方法中,将接收器转换为参数,证明这种限制是不必要的.所以也许我们在Java 10中摆脱了这个限制......


Viv*_*ath 5

我遇到了同样的问题,并且能够通过明确指定通用类型参数来解决该问题,map如下所示:

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