it-roy-ru.com

java лямбда возвращая лямбду

Я пытаюсь сделать то, что кажется относительно простой вещью в новой стране функционального программирования 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());
   }
}

Он берет список чисел и выдает список функций, которые могут их распечатать. Однако явное приведение к 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
21
MK.

Вы столкнулись с ограничением целевой типизации Java 8, которое применяется к получателю вызова метода. Хотя целевая типизация работает (чаще всего) для типов параметров, она не работает для объекта или выражения, для которого вы вызываете метод.

Здесь l.stream(). map(n -> () -> { System.out.println(n); return null; }) является получателем вызова метода collect(Collectors.toList()), поэтому целевой тип List<Callable<Object>> для него не рассматривается.

Легко доказать, что вложенные лямбда-выражения работают, если известен целевой тип, например,.

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 и Generics, программистам пришлось повторить параметры типа в выражениях new, в то время как простая упаковка выражения в универсальный метод доказала, что вывод типа не является проблемой. Прошло до Java 7, прежде чем программистам разрешили пропустить это ненужное повторение аргументов типа (используя «оператор ромба»). Теперь у нас похожая ситуация: перенос выражения вызова в другой метод, превращение получателя в параметр, доказывает, что это ограничение не нужно. Так что, возможно, мы избавимся от этого ограничения в Java 10 ...

23
Holger

Я столкнулся с этой же проблемой и смог ее решить, явно указав универсальный параметр типа для map следующим образом:

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

Я еще не вник в точные правила того, как вывод типов работает с лямбдами. Однако с точки зрения общего языкового дизайна не всегда возможно написать языковые правила, которые позволяют компилятору выяснить все, что мы думаем, что должны. Я был сопровождающим компилятора для языка Ada, и я знаком со многими проблемами дизайна языка там. Ada использует вывод типов во многих случаях (когда тип конструкции не может быть определен без рассмотрения всего выражения, содержащего конструкцию, что, как мне кажется, имеет место и с этим лямбда-выражением Java). Существуют некоторые языковые правила, которые приводят к тому, что компиляторы отклоняют некоторые выражения как неоднозначные, когда теоретически существует только одна возможная интерпретация. Одна причина, если я правильно помню, состоит в том, что кто-то нашел случай, когда правило, которое позволило бы компилятору выяснить правильную интерпретацию, потребовало бы, чтобы компилятор сделал 17 проходов через выражение, чтобы правильно его интерпретировать.

Поэтому, хотя мы можем думать, что компилятор «должен» что-то выяснить в конкретном случае, это может быть просто невыполнимо. 

2
ajb

Во-первых, вы должны знать, как компилятор получает тип лямбда-выражения. Это достигается путем целевой типизации, что означает тип переменной, которой вы назначаете лямбда-выражение. В вашем случае, если вы

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

Вот как лямбда получает свой тип: Function<Integer, Callable<Object>>

Затем вы должны взглянуть на вывод типа в обобщенном типе: Возвращаемый тип карты - <R> Stream<R>, R будет определяться типом параметра, который вы передали в функцию. Если вы map(x->"some string"), то результатом будет Stream<String>. Теперь это проблема, тип R лямбда-типа. Но лямбда нуждается в целевом типе, который является переменной R.

Рабочий код работает, потому что он явно приводит лямбду к типу. 

0
Lukaka