Интерфейс Consumer в Java - Вопросы

Всего: 5 вопросов

1. 

Когда используется интерфейс java.util.function.Consumer?

Consumer<T> используется тогда, когда объект нужно принять на вход и выполнить над ним действие, ничего не возвращая. Его функциональный дескриптор — T -> void, единственный абстрактный метод — void accept(T t). Смысл интерфейса — побочный эффект: вывод на консоль, запись в лог, изменение состояния объекта, отправка сообщения. Печать в консоль — лишь самый наглядный частный случай; на практике Consumer чаще встречается в сигнатурах методов JDK: Iterable.forEach(), Stream.forEach(), Stream.peek(), Optional.ifPresent(). Если действие должно вернуть значение, нужен не Consumer, а Function.

2. 

Напишите лямбда-выражение с использованием интерфейса java.util.function.Consumer, которое выводит данные объекта Exam.

public class Exam {
    private final String name;
    private final String version;

    public Exam(String name, String version) {
        this.name = name;
        this.version = version;
    }

    public String getName() {
        return name;
    }

    public String getVersion() {
        return version;
    }
}

Тип-параметр Consumer — это любой ссылочный тип, в том числе ваш собственный класс:

Consumer<Exam> printExam = e -> System.out.println(e.getName() + " " + e.getVersion());

printExam.accept(new Exam("Java Core", "21"));   // Java Core 21
printExam.accept(new Exam("Spring", "6"));       // Spring 6

Лямбда получает объект Exam, печатает его поля и ничего не возвращает — ровно то, что описывает дескриптор T -> void.

3. 

Какой метод по умолчанию есть у интерфейса Consumer и как он работает?

У Consumer ровно один метод по умолчанию — andThen():

default Consumer<T> andThen(Consumer<? super T> after) {
    Objects.requireNonNull(after);
    return (T t) -> { accept(t); after.accept(t); };
}

Он возвращает новый составной Consumer, который выполняет два действия подряд над одним и тем же аргументом, строго слева направо. Исходный Consumer при этом не меняется, поэтому результат нужно сохранить или использовать сразу. Исключения не перехватываются: если первое действие бросило исключение, второе не выполнится. Передача null в andThen() сразу приводит к NullPointerException из-за Objects.requireNonNull().

4. 

Чем Consumer отличается от Supplier, Function и Runnable?

Различие только в наличии входа и выхода:

Consumer<T>      void accept(T t)   // T -> void
Supplier<T>      T get()            // void -> T
Function<T, R>   R apply(T t)       // T -> R
Runnable         void run()         // void -> void

Consumer принимает аргумент и ничего не возвращает, Supplier наоборот — аргументов нет, значение возвращается, Function принимает аргумент и возвращает результат, Runnable не принимает и не возвращает ничего. Все четыре интерфейса не хранят состояния и описывают ровно одно действие. Признак неверного выбора: внутри Consumer появляется запись во внешний список или поле, чтобы «вынести» результат наружу — в этом случае нужен Function и map() вместо forEach().

5. 

Какие «родственники» есть у Consumer в пакете java.util.function и зачем они нужны?

Одна разновидность для двух аргументов и несколько для примитивов — последние избавляют от автоупаковки:

BiConsumer<T, U>    void accept(T t, U u)        // (T, U) -> void, например Map.forEach()
IntConsumer         void accept(int value)       // int -> void, IntStream.forEach()
LongConsumer        void accept(long value)      // long -> void
DoubleConsumer      void accept(double value)    // double -> void
ObjIntConsumer<T>   void accept(T t, int value)  // (T, int) -> void, аккумуляторы в collect()

У BiConsumer, IntConsumer, LongConsumer и DoubleConsumer тоже есть andThen() — с параметром того же типа. У ObjIntConsumer и его аналогов метода andThen() нет.

IntConsumer printSquare = n -> System.out.println(n * n);
IntStream.rangeClosed(1, 3).forEach(printSquare);
// 1
// 4
// 9
Страница 1 из 1