Как указать типы функций для методов void (не Void) в Java8?

Я играю с Java 8, чтобы узнать, как функционируют как граждане первого класса. У меня есть следующий фрагмент:

package test;

import java.util.*;
import java.util.function.*;

public class Test {

    public static void myForEach(List<Integer> list, Function<Integer, Void> myFunction) {
      list.forEach(functionToBlock(myFunction));
    }

    public static void displayInt(Integer i) {
      System.out.println(i);
    }


    public static void main(String[] args) {
      List<Integer> theList = new ArrayList<>();
      theList.add(1);
      theList.add(2);
      theList.add(3);
      theList.add(4);
      theList.add(5);
      theList.add(6);
      myForEach(theList, Test::displayInt);
    }
}

То, что я пытаюсь сделать, это передать метод displayInt в метод myForEach с помощью ссылки на метод. К компилятору выдается следующая ошибка:

src/test/Test.java:9: error: cannot find symbol
      list.forEach(functionToBlock(myFunction));
                   ^
  symbol:   method functionToBlock(Function<Integer,Void>)
  location: class Test
src/test/Test.java:25: error: method myForEach in class Test cannot be applied to given ty
pes;
      myForEach(theList, Test::displayInt);
      ^
  required: List<Integer>,Function<Integer,Void>
  found: List<Integer>,Test::displayInt
  reason: argument mismatch; bad return type in method reference
      void cannot be converted to Void

Компилятор жалуется, что void cannot be converted to Void. Я не знаю, как указать тип интерфейса функции в сигнатуре myForEach, чтобы код компилировался. Я знаю, что могу просто изменить возвращаемый тип displayInt на Void, а затем вернуть null. Однако могут быть ситуации, когда невозможно изменить метод, который я хочу передать где-то в другом месте. Есть ли простой способ повторно использовать displayInt, как есть?

Ответ 1

Вы пытаетесь использовать неправильный тип интерфейса. Тип Функция не подходит в этом случае, потому что он получает параметр и имеет возвращаемое значение. Вместо этого вы должны использовать Consumer (ранее известный как Block)

Тип функции объявлен как

interface Function<T,R> {
  R apply(T t);
}

Однако тип Consumer совместим с тем, что вы ищете:

interface Consumer<T> {
   void accept(T t);
}

Таким образом, Consumer совместим с методами, которые получают T и ничего не возвращают (void). И это то, что вы хотите.

Например, если бы я хотел отобразить все элементы в списке, я мог бы просто создать для этого потребителя с помощью лямбда-выражения:

List<String> allJedi = asList("Luke","Obiwan","Quigon");
allJedi.forEach( jedi -> System.out.println(jedi) );

Вы можете видеть выше, что в этом случае лямбда-выражение получает параметр и не имеет возвращаемого значения.

Теперь, если я хочу использовать ссылку на метод вместо лямбда-выражения для создания потребителя этого типа, то мне нужен метод, который получает String и возвращает void, верно?

Я мог бы использовать разные типы ссылок на методы, но в этом случае давайте воспользуемся ссылкой на метод объекта, используя метод println в объекте System.out, например:

Consumer<String> block = System.out::println

Или я мог бы просто сделать

allJedi.forEach(System.out::println);

Метод println подходит, потому что он получает значение и имеет возвращаемый тип void, как и метод accept в Consumer.

Итак, в вашем коде вам нужно изменить сигнатуру вашего метода на что-то вроде:

public static void myForEach(List<Integer> list, Consumer<Integer> myBlock) {
   list.forEach(myBlock);
}

И тогда вы сможете создать потребителя, используя ссылку на статический метод, в вашем случае, выполнив:

myForEach(theList, Test::displayInt);

В конечном счете, вы даже можете полностью избавиться от метода myForEach и просто сделать:

theList.forEach(Test::displayInt);

О функциях граждан первого класса

Как уже было сказано, правда в том, что Java 8 не будет иметь функций первоклассного гражданина, поскольку структурный тип функций не будет добавлен к языку. Java просто предложит альтернативный способ создания реализаций функциональных интерфейсов из лямбда-выражений и ссылок на методы. В конечном счете, лямбда-выражения и ссылки на методы будут привязаны к ссылкам на объекты, поэтому все, что у нас есть, это объекты как первоклассные граждане. Важно то, что функциональность существует, поскольку мы можем передавать объекты в качестве параметров, привязывать их к ссылкам на переменные и возвращать их как значения из других методов, тогда они в значительной степени служат аналогичной цели.

Ответ 2

Когда вам нужно принять функцию в качестве аргумента, которая не принимает аргументов и не возвращает результата (void), на мой взгляд, все же лучше иметь что-то вроде

  public interface Thunk { void apply(); }

где-то в вашем коде. В моих курсах по функциональному программированию слово "thunk" использовалось для описания таких функций. Почему это не в java.util.function вне моего понимания.

В других случаях я нахожу, что даже когда java.util.function действительно имеет что-то, что соответствует сигнатуре, которую я хочу - это все еще не всегда правильно, когда наименование интерфейса не соответствует использованию функции в моем коде. Я полагаю, что аналогичное замечание было сделано в другом месте здесь относительно "Runnable" - это термин, связанный с классом Thread - так что, хотя он может иметь нужную мне подпись, он все же может сбить читателя с толку.

Ответ 3

Установите тип возврата Void вместо void и return null

// Modify existing method
public static Void displayInt(Integer i) {
    System.out.println(i);
    return null;
}

ИЛИ

// Or use Lambda
myForEach(theList, i -> {System.out.println(i);return null;});

Ответ 4

Я чувствую, что вы должны использовать интерфейс Consumer вместо Function<T, R>.

Потребитель - это, по сути, функциональный интерфейс, предназначенный для принятия значения и ничего не возвращать

В вашем случае вы можете создать потребителя в другом месте вашего кода следующим образом:

Consumer<Integer> myFunction = x -> {
    System.out.println("processing value: " + x);    
    .... do some more things with "x" which returns nothing...
}

Затем вы можете заменить свой код myForEach кода:

public static void myForEach(List<Integer> list, Consumer<Integer> myFunction) 
{
  list.forEach(x->myFunction.accept(x));
}

Вы рассматриваете myFunction как первоклассный объект.