Як вказати типи функцій методів void (не Void) у Java8?


143

Я граю з 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?

Відповіді:


244

Ви намагаєтесь використовувати неправильний тип інтерфейсу. Тип Функція неприйнятна в цьому випадку, оскільки вона отримує параметр і має зворотне значення. Натомість слід використовувати споживчий (раніше відомий як Блок)

Тип функції оголошено як

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

Однак тип споживача сумісний з тим, що ви шукаєте:

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

Таким чином, Споживач сумісний із методами, які отримують Т і нічого не повертають (недійсні). І це те, чого ти хочеш.

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

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

Вище бачите, що в цьому випадку вираз лямбда отримує параметр і не має зворотного значення.

Тепер, якщо я хотів використати посилання на метод замість лямбда-виразу для створення споживання цього типу, тоді мені потрібен метод, який отримує String і повертає недійсність, правда ?.

Я міг би використовувати різні типи посилань на метод, але в цьому випадку давайте скористаємося посиланням на 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 просто запропонує альтернативний спосіб створення реалізацій функціональних інтерфейсів за допомогою лямбда-виразів та посилань на методи. Зрештою, лямбда-вирази та посилання на методи будуть прив’язані до посилань на об'єкти, тому все, що ми маємо, - це об'єкти як громадяни першого класу. Важливим є функціонал, оскільки ми можемо передавати об'єкти як параметри, прив’язувати їх до змінних посилань і повертати їх як значення з інших методів, то вони в значній мірі служать подібній меті.


1
До речі, Blockбуло змінено на Consumerостанні випуски API JDK 8
Edwin Dalorzo

4
Останній абзац висвітлює деякі важливі факти: лямбдаські вирази та посилання на методи є більш ніж синтаксичним доповненням. Сам JVM надає нові типи для ефективнішої обробки посилань на методи, ніж з анонімними класами (головне MethodHandle), і використовуючи це, компілятор Java може створити більш ефективний код, наприклад, реалізувати лямбдаси без закриття як статичні методи, тим самим запобігаючи генерації додаткові заняття.
Feuermurmel

7
І правильна версія Function<Void, Void>є Runnable.
OrangeDog

2
@OrangeDog Це не зовсім так. У коментарях до Runnableі Callableінтерфейсів написано , що вони повинні бути використані в поєднанні з нитками . Прикрим наслідком цього є те, що деякі інструменти статичного аналізу (наприклад, Sonar) будуть скаржитися, якщо ви зателефонуєте run()безпосередньо до методу.
Денис

Походячи виключно з тла Java, мені цікаво, які конкретно є випадки, коли Java не може розглядати функції як першокласного громадянина навіть після введення функціональних інтерфейсів та лямбдатів з Java8?
Вівек Сеті

21

Коли вам потрібно прийняти функцію як аргумент, який не бере аргументів і не повертає результату (недійсний), на мою думку, все одно найкраще мати щось на зразок

  public interface Thunk { void apply(); }

десь у вашому коді. У моїх курсах функціонального програмування для опису таких функцій використовувалося слово «тунк». Чому він не в java.util.function не вдається мені зрозуміти.

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


Зокрема Runnable, не допускається перевірених винятків, що робить його марним для багатьох програм. Оскільки Callable<Void>це теж не корисно, вам потрібно визначити власне, здається. Некрасивий.
Джессі Глік

Що стосується перевіреної проблеми винятків, то нові функції Java взагалі борються з цим. це величезний блокатор для таких речей, як API Stream. Дуже поганий дизайн з їх боку.
користувач2223059

0

Встановіть тип повернення 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;});

-2

Я вважаю, що вам слід використовувати споживчий інтерфейс замість 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 як об'єкт першого класу.


2
Що це додає понад існуючі відповіді?
Матвій
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.