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

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

1. обзор

ЦиклforEach, представленный в Java 8, предоставляет программистамa new, concise and interesting way for iterating over a collection.

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

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

2. ОсновыforEach

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

void forEach(Consumer action)

Проще говоря,Javadoc изforEach показывает, что это“performs the given action for each element of the Iterable until all elements have been processed or the action throws an exception.”

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

Например, версияfor-loop итерации и печатиCollection изStrings:

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

Мы можем записать это, используяforEach как:

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

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

Мы используемforEach для перебора коллекции и выполнения определенного действия с каждым элементом. The action to be performed is contained in a class that implements the Consumer interface and is passed to forEach as an argument.

ИнтерфейсConsumer - этоa functional interface (интерфейс с одним абстрактным методом). Он принимает входные данные и не возвращает никакого результата.

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

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

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

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

3.1. Анонимная реализацияConsumer

Мы можем создать экземпляр реализации интерфейса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 as:

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

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

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

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

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

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

4. Работа сforEach

4.1. ИтерацияCollection

Любая итерация типаCollection – list, set, queue 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, который также являетсяCollection:

Queue namesQueue = new ArrayDeque<>(Arrays.asList("Larry", "Steve", "James"));

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

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

Карты неIterable, но они делаютprovide their own variant of forEach that accepts a*BiConsumer*. 

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

Давайте создадимMap с записями:

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

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

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

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

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

для перебора записейMap.

4.3. ИтерацияMap – by iterating entrySet

Мы также можем выполнить итерациюEntrySet of aMap с учетом Iterable’sforEach.

Посколькуthe entries of a Map are stored in a Set called EntrySet, we can iterate that using a forEach:

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

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

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

The main difference between the two of them is that they are different iterators – the enhanced for-loop is an external iterator whereas the new forEach method is an internal one.

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

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

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

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

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

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

5.2. Внешний итератор -for-loop

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

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

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

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

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

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

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

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

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

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