Как указать типы функций для методов 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 как первоклассный объект.