Java Streams ни в коем случае не является новой функцией Java. Они были представлены несколько лет назад с появлением Java 8. Я использую их уже несколько месяцев, и только недавно я обнаружил что-то действительно интересное в потоках. Позволь мне объяснить.

Чтобы правильно понять мое недавнее прозрение, вам нужно понять, что составляет поток. Потоки состоят из нескольких различных частей (называемых потоковыми операциями), которые вместе образуют конвейер потока. Эти части:

  • источник (это может быть много разных вещей, но для простоты давайте придерживаться коллекции, поскольку коллекции являются обычным вариантом использования потоков)
  • ноль или более промежуточных операций (они принимают один поток в качестве ввода и выводят другой поток - подумайте filter или map), и
  • одна терминальная операция (например, count или forEach)

Давайте рассмотрим пример, чтобы прояснить каждую часть конвейера потока. Во-первых, нам нужно начать с источника. В этом примере мы будем работать с марками автомобилей.

String[] carMakes = { "Alfa Romeo", "BMW", "Ford", "Toyota", "Aston Martin", "Audi" };
Stream<String> streamSource = Arrays.stream(carMakes)

Мы объявляем массив carMakes, а затем используемArrays.stream, чтобы преобразовать его в поток. Это обеспечивает источник нашего потокового конвейера.

Далее мы собираемся применить промежуточную операцию. Помните, что они принимают один поток на вход и выводят другой. В нашем примере мы хотим знать, какой автомобиль делает имена начинающимися с буквы A. Filter - подходящая промежуточная операция.

filter(carMake -> carMake.startsWith("A"))

Фильтр просмотрит каждый carMake и вернет его, если он соответствует нашим критериям. Наконец, мы хотим добавить терминальную операцию для печати названий производителей автомобилей, чтобы мы могли видеть, какие из них подпадают под наши критерии. Для этого мы будем использовать операцию forEach.

forEach(carMake -> System.out.println(carMake));

Из этих трех частей мы составляем наш потоковый конвейер. Собирая все вместе, получаем следующее:

String[] carMakes = { "Alfa Romeo", "BMW", "Ford", "Toyota", "Aston Martin", "Audi" };
Stream<String> streamSource = Arrays.stream(carMakes);
streamSource
    .filter(carMake -> carMake.startsWith("A"))
    .forEach(carMake -> System.out.println(carMake));

Итак, каково было мое недавнее прозрение? Потоки Java не работают с вашей коллекцией один раз на этапе конвейера, а вместо этого применяют всю цепочку к каждому элементу в источнике потока, по одному элементу за раз. Погодите, что вы на самом деле имеете в виду? Спасибо за вопрос. Я имею в виду, что вызывается весь конвейер, и каждая операция выполняется для каждого элемента индивидуально. Другими словами, каждая марка автомобиля обрабатывается через весь конвейер потока перед следующей. Итак, сначала идет Alfa Romeo, проходит filter промежуточную операцию, затем forEach терминальную операцию еще до того, как BMW даже начинает конвейер и выполняет filter операцию. Важно отметить, что промежуточные операторы, такие как filter, не собирают все совпадающие элементы перед переходом к следующему этапу конвейера.

Это мощная реализация, которая заметно отличается от того, как другие языки обрабатывают потоки. Например, метод Javascript filter выводит массив, содержащий все совпадающие элементы, поэтому, если у вас есть цепочка операторов, каждый оператор в цепочке работает со всеми элементами и завершается до начала следующего.

Это дает потокам Java действительно интересные преимущества. Например, прежде чем осознать это, вы могли подумать, что следующее было более эффективным, чем приведенный выше пример:

streamSource
    .forEach(carMake -> {
        if(carMake.startsWith("A")) {
            System.out.println(carMake)
        }
    });

Вы думаете, что, вероятно, это было бы примерно так: «Мне нужно только один раз взглянуть на каждый элемент, вместо того, чтобы перебирать их все с помощью оператора filter, прежде чем перебирать результаты сопоставления и распечатывать их в моем forEach». . Как вы теперь знаете, эта логика неверна. Этот пример приведенный выше пример практически ничем не отличается с точки зрения эффективности.

Как пишет Венкат Субраманиам, «Lamda-выражения - это средство доступа к Java 8, но настоящая зависимость - от потоков». Наслаждайтесь новыми знаниями.

Если вы хотите узнать больше о Java Streams, я нашел этот пост Stackify достаточно полезным.