Обмен технологиями

Подробное объяснение Java Stream API: мощного инструмента для эффективной обработки данных коллекции.

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

введение

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

Оглавление

  1. Что такое потоковый API
  2. Основные операции Stream API
  3. Расширенные операции Stream API
  4. Параллельный поток
  5. Практический пример Stream API
  6. Лучшие практики для Stream API
  7. Часто задаваемые вопросы и решения
  8. Подведем итог

Что такое потоковый API

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

Функции

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

Основные операции Stream API

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

Stream API предоставляет различные способы создания Stream.

  1. Создать из коллекции
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();
  • 1
  • 2
  1. Создать из массива
String[] array = {"a", "b", "c"};
Stream<String> stream = Arrays.stream(array);
  • 1
  • 2
  1. использоватьStream.of
Stream<String> stream = Stream.of("a", "b", "c");
  • 1
  1. использоватьStream.generate
Stream<Double> stream = Stream.generate(Math::random).limit(10);
  • 1
  1. использоватьStream.iterate
Stream<Integer> stream = Stream.iterate(0, n -> n + 2).limit(10);
  • 1

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

Промежуточные операции используются для преобразования Stream и лениво оцениваются. К распространенным промежуточным операциям относятся следующие:

  1. filter: используется для фильтрации элементов.
Stream<String> stream = list.stream().filter(s -> s.startsWith("a"));
  • 1
  1. map: используется для сопоставления каждого элемента с соответствующим результатом.
Stream<String> stream = list.stream().map(String::toUpperCase);
  • 1
  1. flatMap: используется для преобразования каждого элемента в поток и последующего объединения его в один поток.
Stream<String> stream = list.stream().flatMap(s -> Stream.of(s.split("")));
  • 1
  1. distinct: используется для удаления дубликатов.
Stream<String> stream = list.stream().distinct();
  • 1
  1. sorted: используется для сортировки.
Stream<String> stream = list.stream().sorted();
  • 1
  1. peek: используется для просмотра каждого элемента во время обработки.
Stream<String> stream = list.stream().peek(System.out::println);
  • 1

Работа терминала

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

  1. forEach: выполнить операцию над каждым элементом.
list.stream().forEach(System.out::println);
  • 1
  1. collect: Преобразование потока в другие формы.
List<String> result = list.stream().collect(Collectors.toList());
  • 1
  1. reduce: Преобразовать элементы в потоке в значение.
Optional<String> result = list.stream().reduce((s1, s2) -> s1 + s2);
  • 1
  1. toArray:Преобразовать поток в массив.
String[] array = list.stream().toArray(String[]::new);
  • 1
  1. count: подсчитать количество элементов.
long count = list.stream().count();
  • 1
  1. anyMatchallMatchnoneMatch: Используется для сопоставления решений.
boolean anyMatch = list.stream().anyMatch(s -> s.startsWith("a"));
boolean allMatch = list.stream().allMatch(s -> s.startsWith("a"));
boolean noneMatch = list.stream().noneMatch(s -> s.startsWith("a"));
  • 1
  • 2
  • 3
  1. findFirstfindAny: используется для поиска элементов.
Optional<String> first = list.stream().findFirst();
Optional<String> any = list.stream().findAny();
  • 1
  • 2

Расширенные операции Stream API

Сортировать

использоватьsortedМетод сортирует поток и может передаваться в компаратор.

List<String> list = Arrays.asList("b", "c", "a");
List<String> sortedList = list.stream().sorted().collect(Collectors.toList());
// 逆序排序
List<String> sortedListDesc = list.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());
  • 1
  • 2
  • 3
  • 4

фильтр

использоватьfilterМетод фильтрации элементов в Stream.

List<String> list = Arrays.asList("a", "b", "c");
List<String> filteredList = list.stream().filter(s -> s.startsWith("a")).collect(Collectors.toList());
  • 1
  • 2

картографирование

использоватьmapМетод отображает элементы в потоке.

List<String> list = Arrays.asList("a", "b", "c");
List<String> mappedList = list.stream().map(String::toUpperCase).collect(Collectors.toList());
  • 1
  • 2

Статут

использоватьreduceМетод уменьшения элементов в потоке.

List<String> list = Arrays.asList("a", "b", "c");
String result = list.stream().reduce("", (s1, s2) -> s1 + s2);
  • 1
  • 2

собирать

использоватьcollectМетоды преобразуют Stream в другие формы.

List<String> list = Arrays.asList("a", "b", "c");
List<String> collectedList = list.stream().collect(Collectors.toList());
Set<String> collectedSet = list.stream().collect(Collectors.toSet());
String joinedString = list.stream().collect(Collectors.joining(","));
  • 1
  • 2
  • 3
  • 4

Параллельный поток

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

List<String> list = Arrays.asList("a", "b", "c");
List<String> parallelList = list.parallelStream().map(String::toUpperCase).collect(Collectors.toList());
  • 1
  • 2

Также можно использоватьparallelМетод преобразует обычный поток в параллельный поток.

List<String> list = Arrays.asList("a", "b", "c");
List<String> parallelList = list.stream().parallel().map(String::toUpperCase).collect(Collectors.toList());
  • 1
  • 2

Следует отметить, что параллельный Stream не всегда быстрее последовательного Stream, и его необходимо тестировать в соответствии с конкретной ситуацией.

Практический пример Stream API

Обработка данных сбора

Случай 1. Фильтрация и преобразование коллекций

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

List<String> list = Arrays.asList("a", "ab", "abc", "abcd");
List<String> result = list.stream()
    .filter(s -> s.length() >= 3)
    .map(String::toUpperCase)
    .collect(Collectors.toList());
System.out.println(result); // 输出:[ABC, ABCD]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
Случай 2: Рассчитать среднее значение

Учитывая набор целых чисел, вычислите среднее значение всех целых чисел.

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
OptionalDouble average = list.stream()
    .mapToInt(Integer::intValue)
    .average();
average.ifPresent(System.out::println); // 输出:3.0
  • 1
  • 2
  • 3
  • 4
  • 5

Операции с файлами

Случай 3. Чтение содержимого файла.

Использование API потока

Прочитайте содержимое файла и выведите на консоль.

try (Stream<String> lines = Files.lines(Paths.get("example.txt"))) {
    lines.forEach(System.out::println);
} catch (IOException e) {
    e.printStackTrace();
}
  • 1
  • 2
  • 3
  • 4
  • 5
Случай 4. Подсчитайте количество вхождений слов.

Прочитайте содержимое файла и подсчитайте количество вхождений каждого слова.

try (Stream<String> lines = Files.lines(Paths.get("example.txt"))) {
    Map<String, Long> wordCount = lines
        .flatMap(line -> Arrays.stream(line.split("\W+")))
        .collect(Collectors.groupingBy(String::toLowerCase, Collectors.counting()));
    wordCount.forEach((word, count) -> System.out.println(word + ": " + count));
} catch (IOException e) {
    e.printStackTrace();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

Операции с базой данных

Случай 5. Обработка результатов запроса к базе данных

Допустим, у нас есть таблица базы данных.users, содержащий поляidnameиage . Мы можем использовать Stream API для обработки результатов запроса.

List<User> users = queryDatabase();
List<String> names = users.stream()
    .filter(user -> user.getAge() > 18)
    .map(User::getName)
    .collect(Collectors.toList());
System.out.println(names);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Лучшие практики для Stream API

  1. Избегайте ненужного распараллеливания: Параллельный поток не всегда работает быстрее, и его следует выбирать в каждом конкретном случае.
  2. Правильное использование промежуточных операций и терминальных операций: Промежуточные операции оцениваются лениво и оцениваются только при выполнении терминальной операции.
  3. Обратите внимание на возможность повторного использования Stream: как только поток будет использован, его нельзя будет использовать снова. Если вам нужно использовать его повторно, вы можете рассмотреть возможность преобразования потока в коллекцию перед его использованием.
  4. Используйте правильный коллекторCollectorsВ классе представлены разнообразные коллекторы, и вы можете выбрать подходящий коллектор в соответствии с конкретными потребностями.
  5. Обработка исключений: При использовании Stream API необходимо обрабатывать возможные исключения, особенно в операциях с файлами и операциями с базами данных.

Часто задаваемые вопросы и решения

Трансляция закрыта

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

List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();
stream.forEach(System.out::println);
stream.forEach(System.out::println); // 会抛出IllegalStateException
  • 1
  • 2
  • 3
  • 4

Проблемы с производительностью

Параллельные потоки не всегда быстрее последовательных потоков, и их необходимо тестировать в каждом конкретном случае.можешь использоватьForkJoinPoolЧтобы оптимизировать производительность параллельного Stream.

ForkJoinPool customThreadPool = new ForkJoinPool(4);
customThreadPool.submit(() ->
    list.parallelStream().forEach(System.out::println)
).get();
  • 1
  • 2
  • 3
  • 4

утечка памяти

При использовании Stream API для обработки больших объемов данных необходимо обратить внимание на проблему утечек памяти.можешь использоватьcloseметод, чтобы закрыть поток, или использоватьtry-with-resourcesоператор автоматически закрывает Stream.

try (Stream<String> lines = Files.lines(Paths.get("example.txt"))) {
    lines.forEach(System.out::println);
} catch (IOException e) {
    e.printStackTrace();
}
  • 1
  • 2
  • 3
  • 4
  • 5

Подведем итог

В этой статье подробно описывается использование Java Stream API, включая базовые операции, расширенные операции, параллельную обработку, практические примеры и лучшие практики. Рационально используя Stream API, разработчики могут значительно упростить код, улучшить читаемость и удобство сопровождения кода, а также повысить эффективность обработки данных. Я надеюсь, что эта статья поможет вам использовать Stream API при разработке Java.

Java Stream API — мощный инструмент для обработки данных коллекции. Благодаря гибкому использованию различных операций можно добиться эффективной обработки данных и потоковых вычислений. Если вы еще не использовали Stream API, рекомендуется как можно скорее изучить и освоить этот мощный инструмент и применить его в своем проекте, чтобы повысить эффективность разработки и качество кода.