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.