Элегантная поза для работы с коллекциями в Java 8 — Stream

Java

GitHub 1.5k ЗвездаПуть к тому, чтобы стать Java-инженером, почему бы тебе не прийти и не узнать?

GitHub 1.5k ЗвездаПуть к тому, чтобы стать Java-инженером, ты правда не хочешь узнать?

GitHub 1.5k ЗвездаПуть к тому, чтобы стать Java-инженером, ты действительно уверен, что не хочешь узнать?

В Java коллекции и массивы — это структуры данных, которые мы часто используем, и над ними необходимо часто выполнять такие операции, как добавление, удаление, модификация, поиск, агрегирование, статистика и фильтрация. Напротив, эти операции также доступны в реляционных базах данных, но до Java 8 обработка коллекций и массивов не очень удобна.

Однако эта проблема была решена в Java 8, API Java 8 добавляет новую абстракцию под названием Stream, которая позволяет обрабатывать данные декларативным способом. Эта статья расскажет, как использовать Stream. В частности, производительность и принципы Stream не являются предметом этой статьи.Если вам интересно, статья будет представлена ​​позже.

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

Потоки обеспечивают высокоуровневую абстракцию над операциями над наборами и представлениями Java интуитивно понятным способом, аналогичным запросу данных из базы данных с помощью операторов SQL.

Stream API может значительно повысить производительность Java-программистов, позволяя программистам писать эффективный, чистый и лаконичный код.

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

Поток обладает следующими особенностями и преимуществами:

  • Нет хранилища. Поток — это не структура данных, это просто представление какого-то источника данных, источником данных может быть массив, контейнер Java или канал ввода-вывода и т. д.
  • Рожден для функционального программирования. Любая модификация потока не изменит базовый источник данных, например, выполнение операции фильтрации в потоке не удаляет отфильтрованные элементы, а создает новый поток, который не содержит отфильтрованных элементов.
  • Ленивое исполнение. Операции над потоками выполняются не сразу, а только тогда, когда пользователю действительно нужен результат.
  • Расходуемость. Поток можно «использовать» только один раз, и он будет недействителен после его прохождения, точно так же, как итератор контейнера, он должен быть перегенерирован, если его нужно пройти снова.

Давайте возьмем пример, чтобы увидеть, что может сделать Stream:

В приведенном выше примере возьмите несколько цветных пластиковых шариков в качестве источника данных, сначала отфильтруйте красные и расплавьте их в случайные треугольники. Снова профильтруйте и удалите маленькие треугольники. Наконец, вычислите периметр оставшейся фигуры.

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

Создание потока

В Java 8 может быть несколько способов создания потоков.

1. Создайте поток из существующей коллекции

В Java 8, в дополнение к добавлению большого количества классов, связанных с потоком, также был улучшен сам класс коллекции, и был добавлен метод потока для преобразования класса коллекции в поток.

List<String> strings = Arrays.asList("Hollis", "HollisChuang", "hollis", "Hello", "HelloWorld", "Hollis");
Stream<String> stream = strings.stream();

Выше создайте поток из существующего списка. В дополнение к этому есть метод parallelStream, создающий параллельный поток для коллекции.

Этот метод создания потока через коллекцию также является более распространенным методом.

2. Создайте поток через Stream

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

Stream<String> stream = Stream.of("Hollis", "HollisChuang", "hollis", "Hello", "HelloWorld", "Hollis");

Как и в приведенном выше коде, создайте и верните поток непосредственно через метод of.

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

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

Ниже приведен список часто используемых промежуточных операций:

filter

Метод filter используется для фильтрации элементов по заданным условиям. Следующий фрагмент кода использует метод filter для фильтрации пустых строк:

List<String> strings = Arrays.asList("Hollis", "", "HollisChuang", "H", "hollis");
strings.stream().filter(string -> !string.isEmpty()).forEach(System.out::println);
//Hollis, , HollisChuang, H, hollis

map

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

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
numbers.stream().map( i -> i*i).forEach(System.out::println);
//9,4,4,9,49,9,25

limit/skip

limit возвращает первые n элементов потока; skip отбрасывает первые n элементов. В следующем фрагменте кода используется метод limit для множителя 4 элементов:

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
numbers.stream().limit(4).forEach(System.out::println);
//3,2,2,3

sorted

Метод sorted используется для сортировки потока. В следующем фрагменте кода для сортировки используется метод sorted:

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
numbers.stream().sorted().forEach(System.out::println);
//2,2,3,3,3,5,7

distinct

Different в основном используется для дедупликации.

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
numbers.stream().distinct().forEach(System.out::println);
//3,2,7,5

Далее мы будем использовать пример и изображение, чтобы продемонстрировать, что происходит, когда Stream обрабатывается фильтром, картой, сортировкой, ограничением и различением.

код показывает, как показано ниже:

List<String> strings = Arrays.asList("Hollis", "HollisChuang", "hollis", "Hello", "HelloWorld", "Hollis");
Stream s = strings.stream().filter(string -> string.length()<= 6).map(String::length).sorted().limit(3)
            .distinct();

Процесс и результаты, полученные на каждом этапе, следующие:

Финальная операция потока

Результатом промежуточной операции Stream по-прежнему является Stream, так как же преобразовать Stream в нужный нам тип? Например, подсчет количества элементов в потоке, замена пакета потока набором и т.д. Для этого требуется терминальная операция

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

java.lang.IllegalStateException: stream has already been operated upon or closed

Как говорится, «в одну и ту же реку дважды не войдешь» — именно это и означает.

Обычная заключительная операция выглядит следующим образом:

forEach

Stream предоставляет метод forEach для перебора всех данных в потоке. Следующий фрагмент кода использует forEach для вывода 10 случайных чисел:

Random random = new Random();
random.ints().limit(10).forEach(System.out::println);

count

count используется для подсчета количества элементов в потоке.

List<String> strings = Arrays.asList("Hollis", "HollisChuang", "hollis","Hollis666", "Hello", "HelloWorld", "Hollis");
System.out.println(strings.stream().count());
//7

collect

collect — это операция сокращения, которая принимает различные методы в качестве параметров для накопления элементов в потоке в итоговый результат:

List<String> strings = Arrays.asList("Hollis", "HollisChuang", "hollis","Hollis666", "Hello", "HelloWorld", "Hollis");
strings  = strings.stream().filter(string -> string.startsWith("Hollis")).collect(Collectors.toList());
System.out.println(strings);
//Hollis, HollisChuang, Hollis666, Hollis

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

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

Суммировать

В этой статье описываются назначение, преимущества и многое другое Stream в Java 8. Также допускается несколько вариантов использования Stream, а именно создание Stream, промежуточные операции и конечные операции.

Есть два способа создать поток, а именно с помощью метода потока класса коллекции и с помощью метода потока потока.

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

Последняя операция Stream может преобразовывать Stream в другие формы, такие как вычисление количества элементов в потоке, преобразование потока в коллекцию и обход элементов.

GitHub 1.5k ЗвездаПуть к тому, чтобы стать Java-инженером, почему бы тебе не прийти и не узнать?

GitHub 1.5k ЗвездаПуть к тому, чтобы стать Java-инженером, ты правда не хочешь узнать?

GitHub 1.5k ЗвездаПуть к тому, чтобы стать Java-инженером, ты действительно уверен, что не хочешь узнать?