Encodera

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

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

Оглавление

Операции над потоками

У потоков десятки операций, но в повседневном коде чаще всего встречается десяток. В этом уроке разберём их по группам: отбор, преобразование, порядок и получение результата.

filter и map

filter оставляет элементы, для которых лямбда вернула true. map превращает каждый элемент в другой — например, строку в её длину:

void main() {
    List<String> words = List.of("кот", "собака", "ёж", "попугай");
 
    List<Integer> lengths = words.stream()
            .map(w -> w.length())
            .toList();
 
    IO.println(lengths);
}

Результат:

[3, 6, 2, 7]

Тип элементов после map может поменяться: был поток строк, стал поток чисел. Метод toList() собирает результат в неизменяемый список.

Порядок и уникальность

sorted() сортирует элементы, distinct() убирает повторы, limit(n) оставляет первые n элементов, а skip(n) пропускает их:

void main() {
    List<Integer> scores = List.of(7, 3, 9, 3, 10, 7, 5);
 
    List<Integer> top = scores.stream()
            .distinct()
            .sorted(Comparator.reverseOrder())
            .limit(3)
            .toList();
 
    IO.println(top);
    IO.println(scores.stream().skip(5).toList());
}

Результат:

[10, 9, 7]
[7, 5]

sorted без аргументов сортирует по возрастанию, а с компаратором — как скажет компаратор. Компараторы подробно разобраны в главе о коллекциях.

Проверки и поиск

anyMatch, allMatch и noneMatch отвечают на вопрос «есть ли / все ли / нет ли». findFirst() возвращает первый элемент, завёрнутый в Optional, потому что подходящего элемента может и не быть:

void main() {
    List<Integer> ages = List.of(15, 22, 17, 30);
 
    IO.println(ages.stream().anyMatch(a -> a >= 18));
    IO.println(ages.stream().allMatch(a -> a >= 18));
 
    Optional<Integer> firstAdult = ages.stream()
            .filter(a -> a >= 18)
            .findFirst();
    IO.println(firstAdult.orElse(0));
}

Результат:

true
false
22

Числовые потоки

Для чисел есть особые потоки IntStream, LongStream и DoubleStream. Перейти к ним помогает mapToInt, после чего доступны sum(), average(), max():

record Item(String name, int price) {}
 
void main() {
    List<Item> cart = List.of(new Item("Хлеб", 3), new Item("Сыр", 12), new Item("Чай", 5));
 
    int total = cart.stream().mapToInt(Item::price).sum();
    double avg = cart.stream().mapToInt(Item::price).average().orElse(0);
 
    IO.println("Итого: $" + total);
    IO.println("Средняя цена: $" + avg);
}

Результат:

Итого: $20
Средняя цена: $6.666666666666667

Item::price — ссылка на метод, короткая запись лямбды i -> i.price(). average() возвращает OptionalDouble: у пустого потока среднего нет. Подробнее числовые потоки и flatMap разобраны в уроке flatMap и числовые потоки.

reduce

reduce сворачивает все элементы в одно значение. Ему передают начальное значение и функцию, которая объединяет накопленный результат со следующим элементом:

void main() {
    List<Integer> numbers = List.of(1, 2, 3, 4, 5);
 
    int product = numbers.stream().reduce(1, (acc, n) -> acc * n);
 
    IO.println(product);
}

Результат:

120

Для сумм, минимумов и максимумов удобнее готовые sum(), min(), max() — reduce нужен, когда готового метода нет.

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

Внутри лямбды нельзя менять обычную локальную переменную — компилятор это запрещает:

void main() {
    int total = 0;
    List.of(1, 2, 3).stream().forEach(n -> total += n);
    IO.println(total);
}

Результат:

Main.java:3: error: local variables referenced from a lambda expression must be final or effectively final
    List.of(1, 2, 3).stream().forEach(n -> total += n);
                                           ^
1 error
error: compilation failed

Локальная переменная, используемая в лямбде, должна быть «фактически финальной» — не меняться после присваивания. Вместо накопления во внешней переменной верните результат из потока: int total = list.stream().mapToInt(n -> n).sum();.

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

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