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

java java-8

87381 просмотра

3 ответа

5275 Репутация автора

Я играю с 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как есть?

Автор: Valentin Источник Размещён: 14.01.2013 01:56

Ответы (3)


194 плюса

57304 Репутация автора

Решение

Вы пытаетесь использовать неправильный тип интерфейса. Тип Функция не подходит в этом случае, потому что он получает параметр и имеет возвращаемое значение. Вместо этого вы должны использовать 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Метод подходит , потому что она принимает значение и имеет тип возвращаемого значения пустоты, так же , как acceptметод в Потребителя.

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

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 просто предложит альтернативный способ создания реализаций функциональных интерфейсов из лямбда-выражений и ссылок на методы. В конечном счете, лямбда-выражения и ссылки на методы будут привязаны к ссылкам на объекты, поэтому все, что у нас есть, это объекты как первоклассные граждане. Важно то, что функциональность существует, поскольку мы можем передавать объекты в качестве параметров, привязывать их к ссылкам на переменные и возвращать их как значения из других методов, тогда они в значительной степени служат аналогичной цели.

Автор: Edwin Dalorzo Размещён: 15.01.2013 01:01

16 плюса

6240 Репутация автора

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

  private interface Thunk { void apply(); }

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

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

Автор: LOAS Размещён: 12.12.2016 10:09

0 плюса

139 Репутация автора

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

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

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

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));
}
Автор: Anthony Anyanwu Размещён: 14.06.2019 09:13
Вопросы из категории :
32x32