Рубрики
Без рубрики

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

Краткое и практическое руководство по Java 8 forEach

Автор оригинала: baeldung.

1. Обзор

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

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

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

Дальнейшее чтение:

Разница между Collection.stream().for Each() и Collection.for Each()

Как вырваться из потока Java forEach

Учебник по потоковому API Java 8

2. Основы forEach

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

void forEach(Consumer action)

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

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

Например, рассмотрим версию for-loop для итерации и печати Коллекции строк :

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);
}

Поэтому любая реализация, например, потребитель, который просто печатает строку |:

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

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

names.forEach(printConsumer);

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

Давайте рассмотрим три наиболее популярных способа использования метода 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 является функциональным интерфейсом, мы можем выразить его в Лямбде:

(argument) -> { //body }

Поэтому наш потребитель печати упрощен:

name -> System.out.println(name)

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

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

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

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

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

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

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

4. Работа С forEach

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

Любая итерация типа Коллекция — список , набор , очередь etc. — имеет тот же синтаксис для использования инструкция 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 карты

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

Java 8 вводит BiConsumer вместо Consumer в Iterable forEach , чтобы действие могло выполняться как с ключом, так и со значением Map одновременно.

Давайте создадим Карту с этими записями:

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

Далее, давайте переберем names Map с помощью Map forEach :

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

Как мы можем видеть здесь, мы использовали Consumer для перебора записей Map :

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

4.3. Итерация по карте с помощью итерации entrySet

Мы также можем выполнить итерацию entrySet карты , используя итерацию forEach .

Поскольку записи карты хранятся в Наборе , называемом entrySet, мы можем повторить это, используя forEach :

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

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

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

Основное различие между ними заключается в том, что они являются разными итераторами. Улучшенная для-петля является внешним итератором, в то время как новый инструкция foreach метод является внутренним.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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