Функциональный интерфейс Consumer в Java

User Rating: 0 / 5

В этой статье мы поговорим о функциональном интерфейсе Consumer, доступном в Java начиная с версии 1.8. Это функциональный интерфейс, который представляет собой операцию, принимающую единственный аргумент и не возвращающую никакого результата. 

Интерфейс полезен, когда необходимо итерироваться по коллекции или выполнить некоторое callback-действие на стороне вызывающего кода.

Рассмотрим следующий пример класса ConsumerExample:

package ru.allineed.samples.consumer_interface;

import ru.allineed.samples.config.Localization;

import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;

public class ConsumerExample {
    static class Animal {
        private String name;
        private String color;

        public Animal(String name, String color) {
            this.name = name;
            this.color = color;
        }

        @Override
        public String toString() {
            return "Animal{" +
                    "name='" + name + '\'' +
                    ", color='" + color + '\'' +
                    '}';
        }
    }

    public static void main(String[] args) {
        List<Animal> animals = Arrays.asList(new Animal("Cat", "black"), new Animal("Dog", "brown"));
        Consumer<Animal> animalConsumer = animal -> System.out.println("Feeding the animal: " + animal);

        // [RU] Передаём экземпляр интерфейса Consumer в forEach:
        // [EN] Passing an instance of Consumer interface into forEach:
        Localization.printLocalized(
                "Передаём экземпляр интерфейса Consumer в forEach:",
                "Passing an instance of Consumer interface into forEach:");
        animals.forEach(animalConsumer);
    }
}

Сразу скажу, опережая вопросы, что данный сниппет используется как часть общего проекта Java-примеров, ссылку на Git-репозиторий которого и инструкцию по запуску всех примеров вы найдете внизу этой статьи. Вызов Localization.printLocalized() - это подготовленный нами утильный метод, внутри фактически представляющий собой System.out.println(), но с поддержкой русского и английского языка в примерах.

Внутри класса ConsumerExample задаётся простой статический класс Animal, который описывает некоторое абстрактное животное и имеет два строковых поля - name (хранит название животного) и color (хранит название цвета животного). Также для класса задан конструктор с двумя параметрами, который призван установить значения данных полей для создаваемого объекта.

В основном классе есть стандартная точка входа - статический метод main(), с которого начинается выполнение нашей программы-примера.

В начале мы создаём список animals, инициализируя его двумя объектами нашего класса Animal:

List<Animal> animals = Arrays.asList(new Animal("Cat", "black"), new Animal("Dog", "brown"));

Добавили в список два объекта - кошку и собаку, установив их цвет в чёрный ("black") и коричневый ("brown"), соответственно.

Далее мы как раз определяем инстанс интерфейса Consumer, передавая ему тип нашего класса Animal и задаём лямбда-выражение, определяющее ту самую операцию, которую нужно будет выполнить над переданным объектом класса. В данном случае мы просто выводим на экран строку "Feeding the animal" ("Кормим животное:") и далее выводим строковое представление нашего объекта животного, которое передаётся в Consumer:

Consumer<Animal> animalConsumer = animal -> System.out.println("Feeding the animal: " + animal);

Как можно видеть, в классе Animal переопределён метод toString(), который как раз выведет строковое представление объекта "животное" с указанием его названия и цвета.

Наконец, в третьей строке метода main() мы пробегаем по нашему списку с животными с помощью оператора forEach, которому на вход подаётся созданный нами экземпляр интерфейса Consumer:

animals.forEach(animalConsumer);

Что в итоге будет происходить при выполнении программы? Оператор forEach выполнит заданную нами операцию вывода на экран строки "Feeding the animal: " вместе со строковым представлением  текущего объекта животное.

В консоли при запуске мы увидим следующий ожидаемый вывод:

Feeding the animal: Animal{name='Cat', color='black'}
Feeding the animal: Animal{name='Dog', color='yellow'}

Process finished with exit code 0


В этом примере мы явно создавали экземпляр интерфейса Consumer с указанием лямбда-выражения для нашей операции. Можно было бы сделать метод main() короче и выполнять операцию по выводу информации о наших животных "на лету", т.е. передавать лямбду сразу в оператор forEach:

public static void main(String[] args) {
	List<Animal> animals = Arrays.asList(new Animal("Cat", "black"), new Animal("Dog", "brown"));
	animals.forEach(animal -> System.out.println("Feeding the animal: " + animal));
}

Результат выполнения ничем не будет отличаться от предыдущего.

Третий вариант, как можно использовать экземпляр функционального интерфейса Consumer - создавать анонимный инстанс и передавать его в оператор forEach:

public static void main(String[] args) {
	List<Animal> animals = Arrays.asList(new Animal("Cat", "black"), new Animal("Dog", "brown"));

	animals.forEach(new Consumer<Animal>() {
		@Override
		public void accept(Animal animal) {
			System.out.println("Feeding the animal: " + animal);
		}
	});
}

Этот вариант выглядит более громоздким, чем предыдущие, более того, если Вы пишете этот код в среде разработки IntelliJ IDEA, то она предложит Вам оптимизировать этот участок и заменить его лямбдой - при наведении на "серый" участок кода Вы увидите сообщение "Anonymous new Consumer<Animal>() can be replaced with lambda":

Давайте рассмотрим ещё один интересный пример, где нас может выручить интерфейс Consumer.

Предположим, у вас есть отдельный метод doSomeOperation(), и список объектов Вы задаёте именно в нём (и как-то их обрабатываете, к примеру). При этом в определённой точке Вам требуется перебрать все объекты в списке и выполнить для них какую-то операцию, которая сама задаётся извне, в коде, вызывающем Ваш отдельный метод.

Тогда для метода doSomeOperation() можно в качестве входного аргумента указать как раз Consumer для достижения этой цели. Вот как может выглядеть код:

public static void main(String[] args) {
	Consumer<Animal> animalConsumer = animal -> System.out.println("Feeding the animal: " + animal);
	doSomeOperation(animalConsumer);
}


public static void doSomeOperation(Consumer<Animal> animalConsumer) {
	List<Animal> animals = Arrays.asList(new Animal("Cat", "black"), new Animal("Dog", "brown"));

	Localization.printLocalized(
                "Перед выполнением операции",
                "Before the operation");

	animals.forEach(animalConsumer);

	Localization.printLocalized(
                "После выполнения операции",
                "After the operation");
}

Обратите внимание, сам Consumer мы задали до вызова метода doSomeOperation() и передаем созданный экземпляр консьюмера в него в качестве параметра. В самом методе задаётся список объектов (в нашем случае, тех же животных, для примера), и дальше выполняются какие-то операции до и после перебора элементов списка.

Результатом программы будет следующий вывод на консоль:

Перед выполнением операции
Feeding the animal: Animal{name='Cat', color='black'}
Feeding the animal: Animal{name='Dog', color='brown'}

После выполнения операции

Process finished with exit code 0

Данный подход полезен, когда Вам необходимо связать разные участки Вашей программы и разделить логику обработки подобным образом.

На этом всё, надеюсь, статья пригодится и поможет чаще использовать полезный функциональный интерфейс Consumer в Ваших программах. Буду благодарен за отзывы или вопросы в комментарии к статье. Удачи!

Яндекс.Метрика