Рубрики
Без рубрики

Введение в потоки Java

Привет! Как core Java, так и Vavr предоставляют потоки, которые являются очень удобным инструментом и вместе с вышесказанным… С пометкой java, учебник, для начинающих.

Привет! Оба ядра Java и Vavr поставляют потоки это очень удобный инструмент и вместе с вышеупомянутой опцией/опцией и попыткой обеспечивает функциональный стиль ваших приложений. Как обычно, мы начнем с ванильной Java – сначала опишем, что такое поток и как строить конвейеры . После этого мы углубимся в поток Var и проверим, чем он отличается от того, что Java дает нам из коробки.

Что такое поток в Java?

Потоки были представлены в Java 8 и были обновлены в следующих выпусках. Документация описывает поток как последовательность элементов, поддерживающих последовательные и параллельные агрегированные операции. Пожалуйста, не путайте слово “поток”: еще до 8-й версии Java имела Входной поток и OutputStream , но эти понятия не имеют ничего общего с героем этого поста. Java Течение , который был представлен в Java 8 , является реализацией шаблона монады – концепции , которая была заимствована из функциональных языков. Там монады обозначают вычисления, которые определяются как последовательность шагов.

Давайте взглянем на простой случай, написанный в традиционной манере:

List names = Arrays.asList("Anna", "Bob", "Carolina", "Denis", "Anna", "Jack", "Marketa", "Simon", "Anna");

for (String name: names){
    if (name.equalsIgnoreCase("Anna")){
        System.out.println(name);
    }
}

Что мы здесь делаем, так это находим все Анны в нашем списке и просто печатаем их. Это простая операция, но, тем не менее, требует от нас написания большого количества кода для такой нелепой задачи! Возьмем еще один фрагмент кода:

List names; // same names as before

names.stream().filter(name->name.equalsIgnoreCase("Anna")).forEach(System.out::println);

Та же задача, но теперь для нее требуется только одна строка кода . Что мы здесь делали? Мы построили трубопровод :

  1. Найти все имена, равные Анна
  2. Распечатайте каждый из них

Этот конвейер состоит из промежуточных ( filter() ) и завершающих ( forEach() ) операций, которые мы рассмотрим позже в этом посте.

Создавать потоки

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

Из коллекций

Это самый простой и очевидный вариант. Интерфейс Java Collection имеет встроенный метод stream() , который возвращает последовательный поток с этой коллекцией в качестве источника. Взгляните на фрагмент кода ниже:

List people;

Stream stream = people.stream();

// do something with stream...

Генерирующие потоки

Если у вас нет набора определенных данных, вы можете сгенерировать данные для потока. Это может быть полезно для экспериментов с методами API потоков. Нам нужно предоставить Поставщика , который используется для генерации случайной последовательности элементов. Метод генерировать возвращает бесконечный последовательный неупорядоченный поток. Вот пример:

DoubleStream numbers = Stream.generate(Math::random);

В этом случае мы генерируем поток со случайным двойным значением. IntStream и Длинный поток также предоставляют специальный метод диапазон , который мы также можем использовать для создания потока. Взгляните на фрагмент кода ниже:

IntStream integers = IntStream.range(1,20);
integers.forEach(System.out::println);

LongStream longs = LongStream.rangeClosed(1,20);
longs.forEach(System.out::println);

В обоих случаях у нас есть диапазон от 1 до 20, но результаты разные. Это связано с тем, что диапазон и диапазон закрыт возвращает диапазон, который может содержать номер верхнего предела или нет. |/диапазон закрыт метод возвращает диапазон, включающий оба предела , в то время как диапазон исключает второе значение из результатов.

Недействительный

Другим статическим методом , используемым для создания потоков , является из Nullable . Это позволяет нам создавать поток, содержащий один элемент или пустой (в случае null ). NB этот метод был введен в Java 9.

Найдите код ниже:

Person anna = null;
Stream personStream = Stream.ofNullable(anna); 

от

Еще один достойный внимания метод создания потоков – of . Существует две перегруженные версии этого метода:

  • из (T-элемента)
  • из (T… элементов)

В первом случае он возвращает последовательный поток, содержащий один элемент T . Во втором случае он возвращает последовательный упорядоченный поток, элементами которого являются указанные значения. NB эта вторая версия использует varargs в качестве аргумента. Этот фрагмент кода иллюстрирует этот метод:

Stream cars = Stream.of(new Car("tesla"), new Car("skoda"), new Car("toyota"), new Car("mazda"));
cars.forEach(System.out::println);

Stream skoda = Stream.of(new Car("skoda"));
skoda.forEach(System.out::println);

повторять

Так же, как ofNullable , этот метод был введен в Java 9. итерация принимает два параметра: начальное значение ( начальное значение ) и |/Унарный оператор это создает последовательность. Метод начинается с начального значения и итеративно применяет заданную функцию для получения следующего элемента. Вот пример:

Stream.iterate(0, i -> i + 2);

Пустой поток

Наконец, мы всегда можем создать пустой поток. NB мы упомянули ofNullable метод, который может возвращать пустой поток, но есть другой подход для получения явно пустого потока. пустой метод возвращает пустой последовательный поток:

Stream empty = Stream.empty();

А как насчет Строителя?

Мы исследовали статические методы, которые используются для создания потоков. Но, несмотря на них, есть еще один способ сделать это: использовать Builder. Течение. Builder позволяет создавать потоки, генерируя элементы по отдельности и добавляя их в builder без временных коллекций или буферов. Позвольте взглянуть на это:

// 1. create builder
Stream.Builder builder = Stream.builder;

// 2. create stream
Stream names = builder.add("anna").add("bob")
                        .add("carolina").add("david")
                        .build();

Builder – это еще один подход к созданию потоков. Мы инициализируем поток . Конструктор экземпляр, а затем, используя метод добавить , заполните его значениями. Наконец, мы преобразуем Builder в Поток методом сборки .

Соберите трубопровод

Мы подробно ознакомились с темой создания потоков и рассмотрели ключевые способы ее выполнения. Теперь, когда мы получили экземпляр потока, мы можем собрать конвейер , чтобы сделать что-то полезное с потоком. С технической точки зрения/| конвейер состоит из источника (сбора, функции генератора); за которым следует ноль или более промежуточных операций и терминальной операции. Приведенный ниже график представляет концепцию трубопровода:

В этом разделе мы кратко рассмотрим роль промежуточных и терминальных операций и рассмотрим наиболее заметные из них.

Промежуточные операции

Промежуточные операции возвращают новый поток и являются ленивыми . Их лень означает, что фактическое вычисление исходных данных выполняется только после вызова операции терминала, а исходные элементы используются только по мере необходимости. Мы можем связать в цепочку несколько промежуточных операций, так как каждая возвращает новый объект Поток . Взгляните на график ниже:

Давайте теперь кратко рассмотрим наиболее часто используемые промежуточные операции.

Фильтр

В начале поста мы уже использовали эту операцию для фильтрации коллекции и поиска подходящих имен. В двух словах, он возвращает новый поток, состоящий из элементов, соответствующих заданному условию. Этот метод принимает предикат , который задает условие.

names.stream().filter(name->name.equalsIgnoreCase("Joe"));

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

Карта

Существует несколько операций с картами, и я решил сгруппировать их под одним заголовком. Давайте начнем с общего метода карта . Он возвращает новый поток, состоящий из результатов применения функции mapper к элементам потока. Вот пример кода:

Stream.of("anna", "benjamin", "carol", "david", "eliska", "frank")
    .map(String::toUpperCase)
    .forEach(System.out::println);

Там у нас также есть исходные данные, которые представляют собой список имен. Мы применяем функцию сопоставления для преобразования имен в СТРОКИ ВЕРХНЕГО РЕГИСТРА. Во всех случаях картограф – это Функция это принимает один аргумент и дает результат. Существуют и другие, специфические картографические операции:

  • mapToInt поток данных, состоящий из результатов применения данной функции отображения
  • Карта двойная двойной поток, состоящий из результатов применения данной функции отображения
  • мапТоЛонг Вдоль потока, состоящего из результатов применения данной функции отображения

Отчетливый

Еще одной примечательной промежуточной операцией в Java Stream API является distinct . Он создает поток различных (уникальные) элементы из данных. С технической точки зрения, метод distinct работает с равными сущностями, чтобы избежать дублирования. Для упорядоченных потоков выбор отдельных элементов стабилен, в то время как для неупорядоченных потоков Java не предоставляет гарантий стабильности.

List numbers = Arrays.asList(1, 1, 2, 3, 3, 4, 5, 5); 
numbers.stream().distinct().forEach(System.out::println); 

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

Сортировать

Сортировка – еще одна важная задача, которую мы должны выполнять с потоками. сортированный метод представляет собой промежуточную операцию, которая обеспечивает поток, состоящий из элементов этого потока, отсортированных в соответствии с естественным порядком . Взгляните на фрагмент кода ниже:

List numbers = Arrays.asList(-9, -18, 0, 25, 4); 
numbers.stream().sorted().forEach(System.out::println); 

Опять же, именно так работает сортировка с числами. С пользовательскими сущностями вам необходимо реализовать Сопоставимые , в противном случае/| ClassCastException будет выдано при выполнении операции терминала. Если вы не реализуете этот интерфейс маркера , вы можете использовать перегруженную отсортированную версию, которая принимает Компаратор в качестве аргумента:

Stream.of("barbora", "daria", "cristopher", "adam", "fritz")
    .sorted((s1, s2) -> {
        return s1.compareTo(s2);
    }).forEach(System.out::println);

Пока

Эти два метода также были добавлены с момента выпуска Java 9: dropWhile и занять Некоторое время . Обе являются промежуточными операциями, которые принимают предикат с условием.

  • отбросить в то время как поток, состоящий из оставшихся элементов этого потока, после удаления самого длинного префикса элементов, соответствующих данному предикату.
  • возьмите While поток, состоящий из самого длинного префикса элементов, взятых из этого потока, которые соответствуют данному предикату.

NB оба работают с упорядоченными потоками.

Взгляните на пример фрагмента кода ниже:

Set numbers = Set.of(1,2,3,4,5,6,7,8);
numbers.stream()
    .takeWhile(x-> x < 5)
    .forEach(System.out::println);

Предел

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

List numbers = Arrays.asList(-9, -18, 0, 12, -5, 92, 13, 50, -75, 25, 4); 
numbers.stream().sorted().limit(5).forEach(System.out::println); 

Терминальные операции

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

Есть несколько примечательных операций с терминалами, которые мы рассмотрим здесь.

Для каждого

Ранее мы использовали эту операцию в большинстве примеров. Этот метод принимает функцию Потребитель , которая определяет действие для выполнения над каждым элементом потока. Вы помните, что в начале поста мы сравнили два способа выполнения этой задачи:

List names = Arrays.asList("Anna", "Bob", "Carolina", "Denis", "Anna", "Jack", "Marketa", "Simon", "Anna");

for (String name: names){
    if (name.equalsIgnoreCase("Anna")){
        System.out.println(name);
    }
}

names.stream().filter(name->name.equalsIgnoreCase("Anna")).forEach(System.out::println);

Мы также использовали здесь ссылку на метод , чтобы сделать код короче и читабельнее. В полном виде это будет выглядеть так:

stream.forEach(name->System.out.println(name));

ПРИМЕЧАНИЕ что для параллельных потоковых конвейеров эта операция не гарантирует соблюдение порядка встреч потока, так как это приведет к потере преимущества параллелизма. Для любого данного элемента действие может быть выполнено в любое время и в любом потоке, который выберет библиотека. Если действие обращается к общему состоянию, оно отвечает за обеспечение необходимой синхронизации.

Собирать

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

Существуют две перегруженные версии метода collect : одна возвращает одиночный результат, а другая возвращает коллекцию . Позвольте подробно рассмотреть:

Stream numbers = Stream.of(1, 2, 3, 4, 5); 
List result = numbers.collect(Collectors.toList()); 

В этом фрагменте кода мы используем встроенный Коллекторы способ сбора потока в список. Существуют и другие полезные методы, которые Java предоставляет нам из коробки:

  • Существуют и другие полезные методы, которые Java предоставляет нам из коробки:
  • Существуют и другие полезные методы, которые Java предоставляет нам из коробки:

Найди

Наконец, есть операции, которые возвращают необязательный объект. Я группирую их вместе, в то время как они являются отдельными методами. Давайте сначала перечислим их:

  • найти Любой
  • Найди первым

У обоих из них нет никаких аргументов, поэтому вы можете задать очень разумный вопрос: как они на самом деле находят данные? . Эти методы работают в сочетании с фильтром , который мы описали ранее. Взгляните на пример:

List names = Arrays.asList("anna", "barbora", "andrew", "benjamin", "carol");

Optional anna = names.stream().filter(name->name.equalsIgnoreCase("anna")).findFirst();
if (anna.isPresent){
    System.out.println("Anna is here!");
} else {
    System.out.println("No Anna there");
}

Здесь мы используем findFirst в сочетании с фильтром для поиска соответствующего результата. Однако это очень искусственный пример: обычно мы этого не делаем, а фильтруем по некоторому шаблону :

// names list

names.stream().filter(name->name.startsWith("A")).findAny().ifPresent(System.out::println);

В обоих случаях мы получили Анну. В чем разница между этими двумя методами? Как следует из названий, соответствующий элемент впервые появился . В нашем случае они обе – Анна. найдите любые возвраты любой соответствующий элемент, который может быть первым, а может и не быть: поведение этой операции явно недетерминировано; можно свободно выбирать любой элемент в потоке.

Мы провели всесторонний обзор Java Stream и описали наиболее известные методы, которые должен знать каждый разработчик (также это неполный список, не стесняйтесь изучать Javadoc ). Поток – это концепция, заимствованная из функциональных языков программирования и лучше работающая с другими функциональными инструментами Java, такими как опции. Поток – это концепция, заимствованная из функциональных языков программирования и лучше работающая с другими функциональными инструментами Java, такими как опции. В качестве альтернативы встроенным инструментам Java мы можем использовать библиотеку Var . В предыдущих частях этой трилогии я уже описывал Вариант и Попробуйте классы.

Вывод

В этом посте была рассмотрена тема потоков – концепция, пришедшая из функциональных языков программирования. Технически, Поток Java представляет собой последовательность элементов, поддерживающих последовательные и параллельные агрегатные операции. Java начала включать API потоков как часть JDK 8 и улучшила их в последующих выпусках. Кроме того, потоки Java – это не единственные потоки. Библиотека Var, которая предлагает дополнения к функциональной Java, также предоставляет собственные потоки, которые немного отличаются от ванильной Java. Во время этого поста мы рассмотрели основные задачи, связанные с потоками Java: создание, построение конвейеров. Хорошего дня!

Оригинал: “https://dev.to/iuriimednikov/introduction-to-java-streams-4k20”