Вступление
Метод forEach() является частью Stream и используется для выполнения
указанной операции, определенной Consumer .
Интерфейс Consumer представляет любую операцию, которая принимает
аргумент в качестве входных данных и не имеет выходных данных. Такое
поведение приемлемо, потому что метод forEach() используется для
изменения состояния программы с помощью побочных эффектов, а не явных
возвращаемых типов.
Следовательно, лучшими целевыми кандидатами для Consumers являются
лямбда-функции и ссылки на методы. Стоит отметить, что forEach() можно
использовать в любой Collection .
forEach () в списке
Метод forEach() - это терминальная операция, что означает, что после
вызова этого метода поток вместе со всеми его интегрированными
преобразованиями будет материализован. Другими словами, они «обретут
сущность», а не будут транслироваться.
Сформируем небольшой список:
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
Традиционно вы могли написать цикл для каждого, чтобы пройти через него:
for (Integer element : list) {
System.out.print(element + " ");
}
Это напечатает:
1 2 3
В качестве альтернативы мы можем использовать метод forEach() в
Stream :
list.stream().forEach((k) -> {
System.out.print(k + " ");
});
Это также выводит:
1 2 3
Мы можем сделать это еще проще с помощью ссылки на метод:
list.stream().forEach(System.out::println);
forEach () на карте
Метод forEach() действительно полезен, если мы хотим избежать цепочки
многих потоковых методов. Давайте сгенерируем карту с несколькими
фильмами и их соответствующими оценками IMDB:
Map<String, Double> map = new HashMap<String, Double>();
map.put("Forrest Gump", 8.8);
map.put("The Matrix", 8.7);
map.put("The Hunt", 8.3);
map.put("Monty Python's Life of Brian", 8.1);
map.put("Who's Singin' Over There?", 8.9);
Теперь давайте распечатаем значения каждого фильма с оценкой выше 8.4
:
map.entrySet()
.stream()
.filter(entry -> entry.getValue() > 8.4)
.forEach(entry -> System.out.println(entry.getKey() + ": " + entry.getValue()));
Это приводит к:
Forrest Gump: 8.8
The Matrix: 8.7
Who's Singin' Over There?: 8.9
Здесь мы преобразовали Map в Set помощью entrySet() , передали ее,
отфильтровали на основе оценки и, наконец, распечатали их с помощью
forEach() . Вместо того, чтобы основывать это на возвращении
filter() , мы могли бы основывать нашу логику на побочных эффектах и
пропустить метод filter() :
map.entrySet()
.stream()
.forEach(entry -> {
if (entry.getValue() > 8.4) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
);
Это приводит к:
Forrest Gump: 8.8
The Matrix: 8.7
Who's Singin' Over There?: 8.9
Наконец, мы можем опустить stream() и filter() , начав с forEach()
в начале:
map.forEach((k, v) -> {
if (v > 8.4) {
System.out.println(k + ": " + v);
}
});
Это приводит к:
Forrest Gump: 8.8
The Matrix: 8.7
Who's Singin' Over There?: 8.9
forEach () в наборе
Давайте посмотрим, как мы можем использовать метод forEach Set в
более осязаемом контексте. Во-первых, давайте определим класс,
представляющий Employee компании:
public class Employee {
private String name;
private double workedHours;
private double dedicationScore;
// Constructor, Getters and Setters
public void calculateDedication() {
dedicationScore = workedHours*1.5;
}
public void receiveReward() {
System.out.println(String
.format("%s just got a reward for being a dedicated worker!", name));
}
}
Представляя себя менеджером, мы хотим выбрать некоторых сотрудников,
которые работали сверхурочно, и наградить их за усердный труд. Сначала
сделаем Set :
Set<Employee> employees = new HashSet<Employee>();
employees.add(new Employee("Vladimir", 60));
employees.add(new Employee("John", 25));
employees.add(new Employee("David", 40));
employees.add(new Employee("Darinka", 60));
Затем давайте посчитаем показатель самоотдачи каждого сотрудника:
employees.stream().forEach(Employee::calculateDedication);
Теперь, когда у каждого сотрудника есть оценка самоотверженности, давайте удалим тех, у кого слишком низкая оценка:
Set<Employee> regular = employees.stream()
.filter(employee -> employee.getDedicationScore() <= 60)
.collect(Collectors.toSet());
employees.removeAll(regular);
Наконец, давайте вознаградим сотрудников за их усердный труд:
employees.stream().forEach(employee -> employee.receiveReward());
А для наглядности распечатаем имена счастливчиков:
System.out.println("Awarded employees:");
employees.stream().map(employee -> employee.getName()).forEach(employee -> System.out.println(employee));
После выполнения приведенного выше кода мы получаем следующий результат:
Vladimir just got a reward for being a dedicated worker!
Darinka just got a reward for being a dedicated worker!
Awarded employees:
Vladimir
Darinka
Побочные эффекты против возвращаемых значений
Смысл каждой команды - оценить выражение от начала до конца. Все, что находится между ними, - это побочный эффект . В этом контексте это означает изменение состояния, потока или переменных без возврата каких-либо значений.
Давайте посмотрим на разницу в другом списке:
List<Integer> targetList = Arrays.asList(1, -2, 3, -4, 5, 6, -7);
Этот подход основан на возвращаемых значениях из ArrayList :
long result1 = targetList
.stream()
.filter(integer -> integer > 0)
.count();
System.out.println("Result: " + result1);
Это приводит к:
Result: 4
И теперь, вместо того, чтобы основывать логику программы на возвращаемом
типе, мы выполним forEach() в потоке и добавим результаты в
AtomicInteger (потоки работают одновременно):
AtomicInteger result2 = new AtomicInteger();
targetList.stream().forEach(integer -> {
if (integer > 0)
result2.addAndGet(1);
});
System.out.println("Result: " + result2);
Заключение
Метод forEach() - действительно полезный метод для перебора коллекций
в Java при функциональном подходе.
В некоторых случаях они могут значительно упростить код и повысить
ясность и краткость. В этой статье мы рассмотрели основы использования
forEach() а затем рассмотрели примеры этого метода для List , Map
и Set .
Мы рассмотрели разницу между циклом for-each forEach() , а также
разницу между логикой, основанной на возвращаемых значениях, и побочными
эффектами.