Java 8 Stream reduce
Данная статья написана командой Vertex Academy. Это одна из статей из нашего Учебника по Java 8. Надеемся, что данная статья Вам будет полезна. Приятного прочтения!
В этой статье мы рассмотрим сбор данных с помощью Stream-ов в Java.
1. Введение
Stream API - новый способ взаимодействия с данными, представляя их в виде конечного потока данных.
С помощью Stream API в Java 8 стало возможно использование стратегии mapReduce
С ней мы сегодня и научимся работать.
2. Sum reduce
Посчитаем сумму чисел в списке
1 2 3 4 5 6 7 8 |
public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 5); Optional<Integer> sum = numbers.stream() .reduce((left, right) -> left + right); sum.ifPresent(System.out::println); //output 11 } |
Метод reduce принимает лямбда-выражение известное как аккумулятор (Accumulator), которое служит для сворачивания данных в одну "кучу".
А тепер посчитаем сумму начиная с 10
1 2 3 4 5 6 7 8 |
public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 5); Integer sum = numbers.stream() .reduce(10, (left, right) -> left + right); System.out.println(sum); //output 11 } |
Перегруженный метод reduce принимает начальное значение (identity) и аккумулятор.
В первом случае результат метода reduce вернул Optional<Integer> т.к. мы не указывали начальное значение.
Во втором случае мы указали начальное значение, и метод reduce уже возвращает обычный Integer.
Попоробуем более сложное выражение
1 2 3 4 5 6 7 8 9 |
public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3); // 1*10 + 2*10 + 3*10 Integer sum = numbers.stream() .reduce(10, (identity, val) -> identity * val, (left, right) -> left + right); System.out.println(sum); //output 60 } |
В данном случае метод reduce принимает три параметра - identity, accumulator, combiner. Где accumulator умножает каждое значение из Stream-a на начальное значение (identity) а combiner собирает результат работы accumulator.
Выходит что наш Stream преобразовывается из 1, 2, 3 в 10, 20, 30 а послее просто суммируется.
3. Search reduce
3.1 Search min value
С помощью map reduce можно так же производить поиск
Найдем наименьшее число в массиве
1 2 3 4 5 6 7 8 |
public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 5, 7); Integer min = numbers.stream() .reduce(Integer.MAX_VALUE, (left, right) -> left < right ? left : right); System.out.println(min); //output 1 } |
В данном примере мы указали начальное значение и аккумулятор который и оставляет меньшее значение.
Так же пример можно улучшить с помощью ссылки на метод min в Integer
1 2 3 4 5 6 7 8 |
public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 5, 7); Integer min = numbers.stream() .reduce(Integer.MAX_VALUE, Integer::min); System.out.println(min); //output } |
3.2 Search longest string
Попробуем найти самую длинную строку
1 2 3 4 5 6 7 8 |
public static void main(String[] args) { List<String> strings = Arrays.asList("aaa", "bbb", "ccc", "ddd", "ffff"); String s = strings.stream() .reduce("", (left, right) -> left.length() > right.length() ? left : right); System.out.println(s); //output ffff } |
3.3 Complex search
Создадим класс Connection
1 2 3 4 5 |
class Connection { private String from; private String to; // constructors, getters } |
а так же список всех связей, создав таким образом сеть
1 2 3 4 5 |
List<Connection> network = Arrays.asList(new Connection("A", "B"), new Connection("A", "C"), new Connection("A", "D"), new Connection("B", "C") ); |
А теперь попробуем найти все связи, которые знает узел "A" с помощью mapReduce + filter
Для этого нам понадобится создать наш identity
1 |
List<String> identity = new ArrayList<>(); |
а так же accumulator
1 2 3 4 |
BiFunction<List<String>, Connection, List<String>> accumulator = (strings, connection) -> { strings.add(connection.getTo()); return strings; }; |
где мы просто добавляем наши входящие узлы в список (identity) и возвращаем его.
И combiner
1 2 3 4 |
BinaryOperator<List<String>> combiner = (strings, strings2) -> { strings.addAll(strings2); return strings; }; |
который соединяет два списка узлов в один.
А теперь используем все компоненты
1 2 3 4 5 |
List<String> list = network.stream() .filter(p -> "A".equals(p.getFrom())) .reduce(identity, accumulator, combiner); System.out.println(list); //output [B, C, D] |
Таким образом мы получили список узлов, которые знает узел "А"
Полный код примера
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public static void main(String[] args) { List<Connection> network = Arrays.asList(new Connection("A", "B"), new Connection("A", "C"), new Connection("A", "D"), new Connection("B", "C") ); List<String> identity = new ArrayList<>(); BiFunction<List<String>, Connection, List<String>> accumulator = (strings, connection) -> { strings.add(connection.getTo()); return strings; }; BinaryOperator<List<String>> combiner = (strings, strings2) -> { strings.addAll(strings2); return strings; }; List<String> list = network.stream() .filter(p -> "A".equals(p.getFrom())) .reduce(identity, accumulator, combiner); System.out.println(list); //output [B, C, D] } |