Керівництво по Java 8 forEach

1. Огляд

Введений в Java 8, цикл forEach надає програмістам новий, стислий та цікавий спосіб ітерації над колекцією .

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

Якщо вам потрібно розібратися з деякими концепціями Java 8, ми маємо збірник статей, які можуть вам допомогти.

2. Основи forEach

У Java інтерфейс Collection має Iterable як супер-інтерфейс - і починаючи з Java 8, цей інтерфейс має новий API:

void forEach(Consumer action)

Простіше кажучи, Javadoc forEach стверджує, що він «виконує задану дію для кожного елемента Iterable, поки всі елементи не будуть оброблені або дія не видасть виняток».

Отже, за допомогою forEach ми можемо перебирати колекцію та виконувати задану дію з кожним елементом, як і будь-який інший ітератор.

Наприклад, для циклу версії переборі і друку Колекції з рядків :

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

Ми можемо написати це, використовуючи forEach як:

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

3. Використання методу forEach

Ми використовуємо forEach для перебору колекції та виконання певної дії з кожним елементом. Дія, яку потрібно виконати, міститься у класі, який реалізує споживчий інтерфейс, і передається 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);

Але це не єдиний спосіб створення дії через споживача та використання forEach API.

Давайте розберемо 3 найпопулярніші способи, якими ми будемо використовувати метод forEach :

3.1. Впровадження анонімних споживачів

Ми можемо створити екземпляр реалізації споживчого інтерфейсу, використовуючи анонімний клас, а потім застосувати його як аргумент до методу forEach :

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

Це працює добре, але якщо ми проаналізуємо у наведеному вище прикладі, то побачимо, що фактично використовуваною частиною є код всередині методу accept () .

Хоча лямбда-вирази зараз є нормою і простішим способом зробити це, все ж варто знати, як реалізувати споживчий інтерфейс.

3.2. Лямбда-вираз

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

Оскільки споживчий інтерфейс - це функціональний інтерфейс, ми можемо висловити його на ламбда-формі у вигляді:

(argument) -> { //body }

Тому наш printConsumer спрощує:

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. Ітерація над колекцією

Будь-який ітерабель типу Collection - список, набір, черга тощо має однаковий синтаксис для використання 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, який приймає BiConsumer .

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

Давайте створимо карту, що містить записи:

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

Далі давайте переглянемо namesMap, використовуючи Map's forEach :

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

Як ми бачимо тут, ми використовували BiConsumer :

(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

З простої точки зору, обидва цикли надають однакову функціональність - перебирають елементи в колекції.

Головна відмінність між ними полягає в тому, що вони є різними ітераторами - розширений цикл 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.