Руководство по Java 8 forEach

1. Обзор

Цикл forEach, представленный в Java 8, предоставляет программистам новый, краткий и интересный способ перебора коллекции .

В этой статье мы увидим, как использовать forEach с коллекциями, какие аргументы он принимает и чем этот цикл отличается от расширенного цикла for .

Если вам нужно освежить некоторые понятия о Java 8, у нас есть сборник статей, которые могут вам помочь.

2. Основы forEach

В Java интерфейс Collection имеет Iterable в качестве супер-интерфейса, а начиная с Java 8 этот интерфейс имеет новый API:

void forEach(Consumer action)

Проще говоря, Javadoc forEach stats, что он «выполняет заданное действие для каждого элемента Iterable, пока все элементы не будут обработаны или действие не вызовет исключение».

Итак, с помощью forEach мы можем перебирать коллекцию и выполнять заданное действие с каждым элементом, как и любой другой Iterator.

Например, для цикла версии переборе и печати Коллекции из строк :

for (String name : names) { System.out.println(name); }

Мы можем написать это с помощью forEach как:

names.forEach(name -> { System.out.println(name); });

3. Использование метода forEach

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

Потребительский интерфейс представляет собой функциональный интерфейс (интерфейс с одним абстрактным методом). Он принимает ввод и не возвращает результата.

Вот определение:

@FunctionalInterface public interface Consumer { void accept(T t); }

Следовательно, любая реализация, например, потребитель, который просто печатает String :

Consumer printConsumer = new Consumer() { public void accept(String name) { System.out.println(name); }; };

можно передать в forEach в качестве аргумента:

names.forEach(printConsumer);

Но это не единственный способ создать действие через потребителя и использовать forEach API.

Давайте посмотрим 3 наиболее популярных способа использования метода forEach :

3.1. Анонимная потребительская реализация

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

Consumer printConsumer= new Consumer() { public void accept(String name) { System.out.println(name); } }; names.forEach(printConsumer);

Это работает хорошо, но если мы проанализируем приведенный выше пример, мы увидим, что фактически используемая часть - это код внутри метода accept () .

Хотя лямбда-выражения теперь стали нормой и более простым способом сделать это, все же стоит знать, как реализовать интерфейс Consumer .

3.2. Лямбда-выражение

Основное преимущество функциональных интерфейсов Java 8 заключается в том, что мы можем использовать лямбда-выражения для их создания и избегать громоздких реализаций анонимных классов.

Поскольку Consumer Interface является функциональным интерфейсом, мы можем выразить его в Lambda в форме:

(argument) -> { //body }

Таким образом, наш printConsumer упрощается до:

name -> System.out.println(name)

И мы можем передать его forEach как:

names.forEach(name -> System.out.println(name));

С момента появления лямбда-выражений в Java 8 это, вероятно, самый распространенный способ использования метода forEach .

У лямбда-выражений очень реальная кривая обучения, поэтому, если вы только начинаете, в этой статье будут рассмотрены некоторые передовые методы работы с новой функцией языка.

3.3. Ссылка на метод

Мы можем использовать синтаксис ссылки на метод вместо обычного синтаксиса Lambda, когда уже существует метод для выполнения операции над классом:

names.forEach(System.out::println);

4. Работа с forEach

4.1. Итерация по коллекции

Любая итерация типа Collection - list, set, queue и т. Д. Имеет тот же синтаксис для использования forEach.

Следовательно, как мы уже видели, для перебора элементов списка:

List names = Arrays.asList("Larry", "Steve", "James"); names.forEach(System.out::println);

Аналогично для набора:

Set uniqueNames = new HashSet(Arrays.asList("Larry", "Steve", "James")); uniqueNames.forEach(System.out::println);

Или, скажем, для очереди, которая также является коллекцией :

Queue namesQueue = new ArrayDeque(Arrays.asList("Larry", "Steve", "James")); namesQueue.forEach(System.out::println);

4.2. Итерация по карте - использование карты forEach

Карты не являются Iterable , но они предоставляют свой собственный вариант forEach, который принимает BiConsumer .

BiConsumer был введен вместо потребителя в итерации в Foreach так , что действие может быть выполнено как на ключе и стоимости карты одновременно.

Создадим карту с записями:

Map namesMap = new HashMap(); namesMap.put(1, "Larry"); namesMap.put(2, "Steve"); namesMap.put(3, "James");

Затем давайте переберем namesMap, используя forEach в Map :

namesMap.forEach((key, value) -> System.out.println(key + " " + value));

Как мы видим здесь, мы использовали BiConsumer :

(key, value) -> System.out.println(key + " " + value)

для перебора записей карты .

4.3. Перебор Карта - итерируя entrySet

Мы также можем итерируем EntrySet из с карты с помощью итератора в Foreach.

Поскольку записи Map хранятся в Set под названием EntrySet, мы можем повторить это, используя forEach:

namesMap.entrySet().forEach(entry -> System.out.println( entry.getKey() + " " + entry.getValue()));

5. Foreach против For-Loop

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

Основное различие между ними состоит в том, что это разные итераторы: расширенный цикл for - это внешний итератор, тогда как новый метод forEach - внутренний .

5.1. Внутренний итератор - forEach

Этот тип итератора управляет итерацией в фоновом режиме и оставляет программисту просто кодировать то, что должно быть сделано с элементами коллекции.

Вместо этого итератор управляет итерацией и обеспечивает последовательную обработку элементов.

Давайте посмотрим на пример внутреннего итератора:

names.forEach(name -> System.out.println(name));

В приведенном выше методе forEach мы видим, что предоставленный аргумент является лямбда-выражением. Это означает, что методу нужно только знать, что делать, а вся работа по итерации будет выполняться внутри компании.

5.2. Внешний итератор - для цикла

Внешние итераторы смешивают то, что и как должен выполняться цикл.

Перечисления , итераторы и расширенный цикл for - все это внешние итераторы (помните методы iterator (), next () или hasNext () ?). Во всех этих итераторах наша задача - указать, как выполнять итерации.

Рассмотрим этот знакомый цикл:

for (String name : names) { System.out.println(name); }

Хотя мы не вызываем явно методы hasNext () или next () во время итерации по списку, основной код, который заставляет эту итерацию работать, использует эти методы. Это означает, что сложность этих операций скрыта от программиста, но все же существует.

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

6. Заключение

В этой статье мы показали, что цикл forEach удобнее обычного цикла for .

Мы также увидели, как работает метод forEach и какую реализацию можно получить в качестве аргумента, чтобы выполнить действие над каждым элементом коллекции.

Наконец, все фрагменты, использованные в этой статье, доступны в нашем репозитории Github.