Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Коллекторы

Завершающая операция collect собирает элементы потока в коллекцию, строку или одно число. Что именно получится, решает коллектор — объект, который описывает способ сборки. Готовые коллекторы лежат в классе Collectors.

collect и toList

Самый простой коллектор собирает элементы в список:

void main() {
    List<String> cities = Stream.of("Рим", "Осло", "Лима")
            .collect(Collectors.toList());
 
    IO.println(cities);
}

Результат:

[Рим, Осло, Лима]

С Java 16 у потока есть и короткий метод toList(). Разница в том, что toList() возвращает неизменяемый список, а Collectors.toList() — обычный ArrayList, в который можно добавлять элементы. В новом коде чаще пишут toList().

Множество и строка

Collectors.toSet() собирает элементы во множество и тем самым убирает повторы. Collectors.joining склеивает строки через разделитель, а ещё может добавить начало и конец:

void main() {
    List<String> tags = List.of("java", "код", "java", "урок");
 
    Set<String> unique = tags.stream().collect(Collectors.toSet());
    IO.println(unique.size());
 
    String line = tags.stream().distinct().collect(Collectors.joining(", ", "[", "]"));
    IO.println(line);
}

Результат:

3
[java, код, урок]

Порядок элементов в HashSet не гарантирован, поэтому здесь выводится только размер множества.

toMap

Collectors.toMap строит словарь. Ему передают две функции: одна получает из элемента ключ, другая — значение:

record Product(String name, int price) {}
 
void main() {
    List<Product> products = List.of(new Product("Чай", 5), new Product("Кофе", 9));
 
    Map<String, Integer> prices = products.stream()
            .collect(Collectors.toMap(Product::name, Product::price));
 
    IO.println(prices.get("Кофе"));
}

Результат:

9

Подсчёты

Некоторые коллекторы сразу считают итог: counting() — количество, summingInt — сумму, averagingDouble — среднее:

record Order(String customer, double amount) {}
 
void main() {
    List<Order> orders = List.of(
            new Order("Анна", 20.0),
            new Order("Олег", 35.5),
            new Order("Анна", 12.5));
 
    long count = orders.stream().collect(Collectors.counting());
    double avg = orders.stream().collect(Collectors.averagingDouble(Order::amount));
 
    IO.println("Заказов: " + count);
    IO.println("Средний чек: €" + avg);
}

Результат:

Заказов: 3
Средний чек: €22.666666666666668

Сами по себе эти коллекторы выглядят длиннее, чем count() или average(). Их настоящая сила раскрывается в группировке — о ней следующий урок.

Неизменяемые коллекции

Если результат не нужно менять, используйте Collectors.toUnmodifiableList(), toUnmodifiableSet() или toUnmodifiableMap(). Попытка изменить такую коллекцию выбросит UnsupportedOperationException — это защищает данные от случайной порчи.

Частая ошибка

toMap падает, если два элемента дают одинаковый ключ:

record Order(String customer, double amount) {}
 
void main() {
    List<Order> orders = List.of(new Order("Анна", 20.0), new Order("Анна", 12.5));
 
    Map<String, Double> byCustomer = orders.stream()
            .collect(Collectors.toMap(Order::customer, Order::amount));
}

Результат:

Exception in thread "main" java.lang.IllegalStateException: Duplicate key Анна (attempted merging values 20.0 and 12.5)
	at Main.main(Main.java:7)

Если повторы возможны, передайте третьим аргументом функцию слияния — она решает, что делать с двумя значениями одного ключа. Например, Double::sum сложит суммы заказов: Collectors.toMap(Order::customer, Order::amount, Double::sum).

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.