Ръководство за 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-loop версия на итерация и отпечатване на колекция от низове :

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 .

Lambdas наистина имат истинска крива на обучение, така че ако започвате, това писане обхваща някои добри практики за работа с новата езикова функция.

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. Итерация върху карта - Използване на Map forEach

Картите не са подлежащи на поддръжка , но те предоставят собствен вариант на forEach, който приема BiConsumer .

В Iterable 's forEach е въведен BiConsumer вместо Consumer , за да може едновременно да се извърши действие както върху ключа, така и върху стойността на карта .

Нека създадем карта, съдържаща записи:

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

След това нека повторим namesMap, използвайки Map forEach :

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

Както виждаме тук, използвахме BiConsumer :

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

да прегледате записите на Картата .

4.3. Итерация по карта - чрез итериране на entrySet

Също така можем да повторим EntrySet на карта, използвайки forteach на Iterable .

Тъй като записите на карта се съхраняват в набор, наречен EntrySet, можем да повторим това, използвайки forEach:

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

5. Foreach срещу For-Loop

От проста гледна точка, и двата цикъла осигуряват една и съща функционалност - обикалят елементи в колекция.

Основната разлика между двамата е, че те са различни итератори - усъвършенстваният for-loop е външен итератор, докато новият метод forEach е вътрешен .

5.1. Вътрешен Итераторът - forEach

Този тип итератор управлява итерацията във фонов режим и оставя програмиста просто да кодира това, което трябва да се направи с елементите на колекцията.

Вместо това итераторът управлява итерацията и се уверява, че обработва елементите един по един.

Да видим пример за вътрешен итератор:

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

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

5.2. Външен итератор - за цикъл

Външните итератори смесват какво и как трябва да се направи цикълът.

Изброяванията , итераторите и подобреният for-loop са всички външни итератори (не забравяйте методите iterator (), next () или hasNext () ?). Във всички тези итератори нашата работа е да определим как да изпълняваме итерации.

Помислете за този познат цикъл:

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

Въпреки че не извикваме изрично методите hasNext () или next () , докато итерираме по списъка, основният код, който прави тази итерация работи, използва тези методи. Това предполага, че сложността на тези операции е скрита от програмиста, но все още съществува.

Противно на вътрешния итератор, в който колекцията прави самата итерация, тук се изисква външен код, който изважда всеки елемент от колекцията.

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

В тази статия показахме, че цикълът forEach е по-удобен от нормалния for-loop .

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

И накрая, всички фрагменти, използвани в тази статия, са налични в нашето хранилище на Github.