Почему операции streamApi принимают другой параметр без ошибки компиляции?

Рейтинг: 1Ответов: 1Опубликовано: 11.03.2023

Решил для себя подразобраться с method reference и после некоторых тестов пришел к такому примеру:

 public static void main(String[] args) {
    Function<String, String> capitalize = StringUtils::capitalize;
    List<String> messages = Arrays.asList("how", "this", "works?");
    messages.forEach(capitalize); //идея жалуется
    messages.forEach(StringUtils::capitalize); //все в порядке
}

У стримов все тоже самое

public static void main(String[] args) {
    Supplier<Integer> supplier = () -> 42;
    List<String> messages = Arrays.asList("how", "this", "works?");
    messages.stream().map(supplier).forEach(System.out::println); //ошибка
    messages.stream().map(s -> supplier.get()).forEach(System.out::println); //все ок
}

Нет явного понимания, почему так. Я полагаю, что где-то там внутри используется просто абстрактный метод функционального интерфейса или как-то оборачивается это все ... Хотелось бы исправить этот пробел в знаниях

Ответы

▲ 1Принят

На самом деле, в обоих случаях компилятор "ругается" из-за несоответствия типов.

Сначала разберём ваш первый код. Вы пишете method reference для типа Function, и пишете его правильно, но тип входного параметра у метода forEach другой - это Consumer.

Так выглядит функциональный интерфейс Function:

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
    // and other methods
}

Его абстрактный метод принимает на вход значение и возвращает значение.

А вот так выглядит функциональный интерфейс Consumer:

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
    // and other methods
}

Его абстрактный метод принимает на вход значение и ничего не возвращает.

Лямбда-выражение для обоих методов может выглядеть совершенно идентично:

Function<String, String> capitalize = s -> StringUtils.capitalize(s);
Consumer<String> consumer = s -> StringUtils.capitalize(s);

В первом случае мы принимаем строку и возвращаем её, переведённую в верхний регистр, а во втором случае просто принимаем строку, но результат действия с ней не возвращается функцией.

Эти лямбда-выражения можно сократить до method reference, как вы и сделали для Function:

Function<String, String> capitalize = StringUtils::capitalize;
Consumer<String> consumer = StringUtils::capitalize;

Теперь проверим предупреждения компилятора:

public static void main(String[] args) {
    Function<String, String> function = StringUtils::capitalize; // method reference для типа Function
    Consumer<String> consumer = StringUtils::capitalize; // method reference для типа Consumer
    List<String> messages = Arrays.asList("how", "this", "works?");
    messages.forEach(function); // ошибка, ведь передаём переменную типа Function
    messages.forEach(consumer); // ошибки нет, ведь передаём переменную типа Consumer
    messages.forEach(StringUtils::capitalize); // ошибки нет, так как автоматически предполагается тип Consumer
}

Теперь разберём ваш второй код. Вы пишете лямбда-выражение для интерфейса Supplier, и пишете его правильно, но тип входного параметра у метода map другой - это Function.

public static void main(String[] args) {
    Supplier<Integer> supplier = () -> 42; // лямбда-выражение для Supplier
    List<String> messages = Arrays.asList("how", "this", "works?");
    messages.stream().map(supplier).forEach(System.out::println); // ошибка, так как требуется тип Function
    messages.stream().map(() -> 42).forEach(System.out::println); // ошибка, так как требуется тип Function
}

Supplier просто выдаёт значение, не принимая никаких параметров. А Function, как мы убедились выше, принимает параметр и возвращает значение. Код с корректным лямбда-выражением для типа Function и поясняющими комментариями приведён ниже.

public static void main(String[] args) {
    Supplier<Integer> supplier = () -> 42; // лямбда-выражение для Supplier
    Function<String, Integer> function = s -> supplier.get(); // лямбда-выражение для Function: не просто возвращаем значение, но и принимаем параметр
    List<String> messages = Arrays.asList("how", "this", "works?");
    messages.stream().map(function).forEach(System.out::println); // работает
    messages.stream().map(s -> supplier.get()).forEach(System.out::println); // работает
}