Technology Sharing

Java Stream API detailed explanation: a powerful tool for efficient processing of collection data

2024-07-12

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

introduction

Java 8 introduces many new features, the most notable of which are Lambda expressions and Stream API. Stream API provides an efficient and concise way to process collection data, making the code more concise, clear, readable and maintainable. This article will explore the use of Java Stream API in depth, including basic concepts, common operations, parallel processing, practical cases and best practices.

Table of contents

  1. What is Stream API
  2. Basic operations of Stream API
  3. Advanced Operations of Stream API
  4. Parallel Streams
  5. Stream API Practical Examples
  6. Best Practices for Stream API
  7. Common Problems and Solutions
  8. Summarize

What is Stream API

Stream API is an abstraction for processing collection data introduced in Java 8. It allows data to be processed in a declarative way (similar to SQL statements). Stream API provides many powerful operations that can be used to filter, sort, map, reduce, and other operations on collections, greatly simplifying the code.

Features

  • Declarative Programming: Using Stream API, you can write code in a declarative way and reduce boilerplate code.
  • Chain calls: Stream API operations can be chained to improve code readability.
  • Lazy Evaluation: Intermediate operations are lazily evaluated and are only evaluated when the terminal operation is executed.
  • Parallel Processing: Supports parallel processing and can fully utilize the advantages of multi-core CPUs.

Basic operations of Stream API

Creating a Stream

The Stream API provides multiple ways to create Streams. The most common ones are as follows:

  1. Create from Collection
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();
  • 1
  • 2
  1. Create from array
String[] array = {"a", "b", "c"};
Stream<String> stream = Arrays.stream(array);
  • 1
  • 2
  1. useStream.of
Stream<String> stream = Stream.of("a", "b", "c");
  • 1
  1. useStream.generate
Stream<Double> stream = Stream.generate(Math::random).limit(10);
  • 1
  1. useStream.iterate
Stream<Integer> stream = Stream.iterate(0, n -> n + 2).limit(10);
  • 1

Intermediate Operations

Intermediate operations are used to transform Streams and are lazily evaluated. Common intermediate operations include the following:

  1. filter: Used to filter elements.
Stream<String> stream = list.stream().filter(s -> s.startsWith("a"));
  • 1
  1. map: Used to map each element to the corresponding result.
Stream<String> stream = list.stream().map(String::toUpperCase);
  • 1
  1. flatMap: Used to convert each element into a Stream and then merge them into one Stream.
Stream<String> stream = list.stream().flatMap(s -> Stream.of(s.split("")));
  • 1
  1. distinct: Used for deduplication.
Stream<String> stream = list.stream().distinct();
  • 1
  1. sorted: Used for sorting.
Stream<String> stream = list.stream().sorted();
  • 1
  1. peek: Used to view each element during processing.
Stream<String> stream = list.stream().peek(System.out::println);
  • 1

Terminal Operation

Terminal operations are used to start stream calculations and generate results. Common terminal operations include the following:

  1. forEach: Perform an operation on each element.
list.stream().forEach(System.out::println);
  • 1
  1. collect: Convert a Stream to another form.
List<String> result = list.stream().collect(Collectors.toList());
  • 1
  1. reduce: Reduce the elements in the Stream to a value.
Optional<String> result = list.stream().reduce((s1, s2) -> s1 + s2);
  • 1
  1. toArray: Convert a Stream to an array.
String[] array = list.stream().toArray(String[]::new);
  • 1
  1. count: Calculate the number of elements.
long count = list.stream().count();
  • 1
  1. anyMatchallMatchnoneMatch: Used for matching judgment.
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: Used to find elements.
Optional<String> first = list.stream().findFirst();
Optional<String> any = list.stream().findAny();
  • 1
  • 2

Advanced Operations of Stream API

Sorting

usesortedMethod sorts the Stream and can pass in a comparator.

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

usefilterMethod to filter the elements in the Stream.

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

Mapping

usemapMethod maps the elements in a Stream.

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

Regulations

usereduceMethod to reduce the elements in the Stream.

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

collect

usecollectMethods convert a Stream into another form.

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 Streams

Parallel Stream can take full advantage of multi-core CPUs to improve data processing efficiency.parallelStreamMethod to create a parallel Stream.

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

You can also useparallelmethod converts a normal Stream into a parallel Stream.

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

It should be noted that parallel Stream is not always faster than serial Stream, and specific tests need to be carried out according to specific circumstances.

Stream API Practical Examples

Processing Collection Data

Case 1: Filter and transform a collection

Given a collection of strings, filter out strings with length less than 3 and convert the remaining strings to uppercase.

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
Case 2: Calculating the average

Given a set of integers, calculate the average of all the integers.

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

File Operations

Case 3: Reading file contents

Using Stream API

Read the contents of a file and output them to the console.

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
Case 4: Counting the number of word occurrences

Read the file contents and count the number of times each word occurs.

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

Database Operations

Case 5: Processing database query results

Suppose we have a database tableusers, containing the fieldsidnameandageWe can use the Stream API to process the query results.

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

Best Practices for Stream API

  1. Avoid unnecessary parallelization: Parallel Stream is not always faster and should be chosen based on the specific situation.
  2. Reasonable use of intermediate and terminal operations: Intermediate operations are lazily evaluated and are only evaluated when the terminal operation is executed.
  3. Note the reusability of Stream: Once a Stream is consumed, it cannot be used again. If you need to reuse it, you can consider converting the Stream into a collection for reuse.
  4. Use the right collectorCollectorsThe class provides a variety of collectors, and you can choose the appropriate collector according to your specific needs.
  5. Handling Exceptions: When using the Stream API, you need to handle possible exceptions, especially in file operations and database operations.

Common Problems and Solutions

Stream is closed

Once a Stream is consumed, it cannot be reused. If you need to reuse it, you can consider converting the Stream into a collection for reuse.

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

Performance issues

Parallel Stream is not always faster than serial Stream, you need to test it according to the specific situation.ForkJoinPoolTo optimize the performance of parallel Stream.

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

Memory Leaks

When using the Stream API to process large amounts of data, you need to be aware of memory leaks.closemethod to close the Stream, or usetry-with-resourcesStatement automatically closes the 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

Summarize

This article introduces the use of Java Stream API in detail, including basic operations, advanced operations, parallel processing, practical cases and best practices. By making proper use of Stream API, developers can greatly simplify the code, improve the readability and maintainability of the code, and improve the efficiency of data processing. I hope this article will help you use Stream API in Java development.

Java Stream API is a powerful tool for processing collection data. By flexibly using various operations, efficient data processing and stream computing can be achieved. If you have not used Stream API, it is recommended to learn and master this powerful tool as soon as possible and apply it to your project to improve development efficiency and code quality.