Вступление
Метод 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()
, а также
разницу между логикой, основанной на возвращаемых значениях, и побочными
эффектами.