A Comprehensive Guide to Java 8 Stream Features

A Comprehensive Guide to Java 8 Stream Features
Programmer’s Growth Path
Internet/Programmer/Technology/Resource Sharing
Follow

Reading this article will take approximately 16 minutes.

From: blog.csdn.net/mu_wind/article/details/91464351

1 Basic Features

Java 8’s API introduced a new feature: Stream. A stream treats the elements of an array or collection as a flow, allowing data to be filtered, sorted, and manipulated as it flows through a pipeline.

1.1 Characteristics of Streams

  1. stream does not store data but computes it according to specific rules, typically producing a result;

  2. stream does not modify the data source; it usually produces a new collection;

  3. stream has lazy execution characteristics, where intermediate operations are only executed when terminal operations are called.

  4. Operations on stream can be categorized into terminal operations and intermediate operations. Terminal operations consume the stream and produce a result. Once a stream has been consumed, it cannot be reused. Intermediate operations produce another stream, allowing for the creation of a pipeline of actions. A critical point to note is that intermediate operations do not occur immediately. Instead, the operations specified by intermediate operations only occur after terminal operations are executed on the newly created stream. Thus, intermediate operations are delayed, which allows the Stream API to execute more efficiently.

  5. stream cannot be reused; calling on a stream that has already undergone terminal operations will throw an exception.

1.2 Creating Streams

Creating a stream from an array
public static void main(String[] args) { //1. Creating via Arrays.stream //1.1 Primitive types int[] arr = new int[]{1,2,34,5}; IntStream intStream = Arrays.stream(arr); //1.2 Reference types Student[] studentArr = new Student[]{new Student("s1",29),new Student("s2",27)}; Stream<Student> studentStream = Arrays.stream(studentArr); //2. Creating via Stream.of Stream<Integer> stream1 = Stream.of(1,2,34,5,65); // Note that this generates a stream of int[] Stream<int[]> stream2 = Stream.of(arr,arr); stream2.forEach(System.out::println); }
Creating a stream from a collection
public static void main(String[] args) { List<String> strs = Arrays.asList("11212","dfd","2323","dfhgf"); // Create a regular stream Stream<String> stream  = strs.stream(); // Create a parallel stream Stream<String> stream1 = strs.parallelStream(); }

2 Detailed Stream API

BaseStream is the foundational interface that provides the basic functionality of streams.

2.1 BaseStream Details

The source code for the BaseStream interface is as follows:
public interface BaseStream<T, S extends BaseStream<T, S>> extends AutoCloseable { Iterator<T> iterator(); Spliterator<T> spliterator(); boolean isParallel(); S sequential(); S parallel(); S unordered(); S onClose(Runnable closeHandler); @Override void close(); }
Method details:
A Comprehensive Guide to Java 8 Stream Features

2.2 Stream Details

The source code for the Stream interface is as follows:
public interface Stream<T> extends BaseStream<T, Stream<T>> { Stream<T> filter(Predicate<? super T> predicate); <R> Stream<R> map(Function<? super T, ? extends R> mapper); IntStream mapToInt(ToIntFunction<? super T> mapper); LongStream mapToLong(ToLongFunction<? super T> mapper); DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper); <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper); IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper); LongStream flatMapToLong(Function<? super T, ? extends LongStream> mapper); DoubleStream flatMapToDouble(Function<? super T, ? extends DoubleStream> mapper); Stream<T> distinct(); Stream<T> sorted(); Stream<T> sorted(Comparator<? super T> comparator); Stream<T> peek(Consumer<? super T> action); Stream<T> limit(long maxSize); Stream<T> skip(long n); void forEach(Consumer<? super T> action); void forEachOrdered(Consumer<? super T> action); Object[] toArray(); <A> A[] toArray(IntFunction<A[]> generator); T reduce(T identity, BinaryOperator<T> accumulator); Optional<T> reduce(BinaryOperator<T> accumulator); <U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner); <R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner); <R, A> R collect(Collector<? super T, A, R> collector); Optional<T> min(Comparator<? super T> comparator); Optional<T> max(Comparator<? super T> comparator); long count(); boolean anyMatch(Predicate<? super T> predicate); boolean allMatch(Predicate<? super T> predicate); boolean noneMatch(Predicate<? super T> predicate); Optional<T> findFirst(); Optional<T> findAny(); // Static factories public static<T> Builder<T> builder() { return new Streams.StreamBuilderImpl<>(); } public static<T> Stream<T> empty() { return StreamSupport.stream(Spliterators.<T>emptySpliterator(), false); } public static<T> Stream<T> of(T t) { return StreamSupport.stream(new Streams.StreamBuilderImpl<>(t), false); } @SafeVarargs @SuppressWarnings("varargs") // Creating a stream from an array is safe public static<T> Stream<T> of(T... values) { return Arrays.stream(values); } public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) { Objects.requireNonNull(f); final Iterator<T> iterator = new Iterator<T>() { @SuppressWarnings("unchecked") T t = (T) Streams.NONE; @Override public boolean hasNext() { return true; } @Override public T next() { return t = (t == Streams.NONE) ? seed : f.apply(t); } }; return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED | Spliterator.IMMUTABLE), false); } public static<T> Stream<T> generate(Supplier<T> s) { Objects.requireNonNull(s); return StreamSupport.stream(new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef<>(Long.MAX_VALUE, s), false); } public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) { Objects.requireNonNull(a); Objects.requireNonNull(b); @SuppressWarnings("unchecked") Spliterator<T> split = new Streams.ConcatSpliterator.OfRef<>((Spliterator<T>) a.spliterator(), (Spliterator<T>) b.spliterator()); Stream<T> stream = StreamSupport.stream(split, a.isParallel() || b.isParallel()); return stream.onClose(Streams.composedClose(a, b)); } }
Descriptions of some important methods:
A Comprehensive Guide to Java 8 Stream Features

3 Common Methods

3.1 Data Used for Demonstration

A Person entity class is created below, serving as demonstration data. The Person class has two attributes: name and salary.
public class Person { private String name; private int salary; // Constructor public Person(String name, int salary) { this.name = name; this.salary = salary; } // Omitted getter and setter methods } public class MyTest { public static void main(String[] args) { List<Person> personList= new ArrayList<Person>(); persons.add(new Person("Tom", 8900)); persons.add(new Person("Jack", 7000)); persons.add(new Person("Lily", 9000)); } }

3.2 Filtering and Matching

Filtering streams, or filter, is an operation that extracts elements from the stream that meet certain criteria. The filter is usually combined with collect to gather the filtered results into a new collection.
Matching streams is similar to filtering but returns a single element or result. Filtering for primitive types:
public static void main(String[] args) { List<Integer> intList = Arrays.asList(6, 7, 3, 8, 1, 2, 9); List<Integer> collect = intList.stream().filter(x -> x > 7).collect(Collectors.toList()); System.out.println(collect); // Expected result: [8,9] }
Filtering for reference types:
public static void main(String[] args) { List<Person> collect = personList.stream().filter(x -> x.getSalary() > 8000).collect(Collectors.toList()); // Expected result: A collection of entities that meet the criteria }
Matching:
public static void main(String[] args) { List<Integer> list = Arrays.asList(7,6,9,3,8,2,1); // Match the first Optional<Integer> findFirst = list.stream().filter(x -> x > 6).findFirst(); // Match any (suitable for parallel streams) Optional<Integer> findAny = list.parallelStream().filter(x -> x > 6).findAny(); // Check if any match boolean anyMatch = list.stream().anyMatch(x -> x < 6); System.out.println(findFirst); System.out.println(findAny); System.out.println(anyMatch); } // Expected results: // 1. Optional[7] // 2. Result uncertain for parallel stream // 3. true 

3.3 Aggregation

In streams, aggregation operations yield results from a stream, such as sums or maximum values. Aggregation operations broadly include methods like max, min, count, as well as reduce and collect.

3.3.1 max, min, and count

1. Get the longest element from a String collection:
public static void main(String[] args) { List<String> list = Arrays.asList("adnm","admmt","pot"); Optional<String> max = list.stream().max(Comparator.comparing(String::length)); System.out.println(max); // Expected result: Optional[admmt] }
2. Get the maximum value from an Integer collection:
public static void main(String[] args) { List<Integer> list = Arrays.asList(7,6,9); Optional<Integer> reduce = list.stream().max(new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o1.compareTo(o2); } }); System.out.println(reduce); // Output result: Optional[9] }
3. Minimum value from an object collection (Person as demonstration data):
public static void main(String[] args) { list.add(new Person("a", 4)); list.add(new Person("b", 4)); list.add(new Person("c", 6)); Optional<Person> max = list.stream().max(Comparator.comparingInt(Person::getSalary)); System.out.println(max.get().getSalary()); // Output result: 6; change max to min for minimum value. }
4. Count:
public static void main(String[] args) { List<Integer> list = Arrays.asList(7,6,9); long count = list.stream().filter(x -> x > 6).count(); System.out.println(count); // Expected result: 2 }

3.3.2 Reduction (reduce)

Reduction operations condense a stream into a single value, such as summing a collection or calculating a product.
Stream defines three reduce methods:
public interface Stream<T> extends BaseStream<T, Stream<T>> { // Method 1 T reduce(T identity, BinaryOperator<T> accumulator); // Method 2 Optional<T> reduce(BinaryOperator<T> accumulator); // Method 3 <U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner); }
The first two reduction methods:
The first reduction method takes a BinaryOperator accumulator function (binary accumulation function) and identity (a marker value) as parameters, returning an object of type T (representing the type of elements in the stream). The accumulator represents the function that operates on two values to produce a result. The identity participates in the calculation according to the accumulator’s function; for example, if the function is sum, the sum result plus identity is the final result. If the function is multiplication, the result of the function multiplied by identity is the final result.
The second reduction method differs in that it does not have an identity; it returns an Optional (a new class in JDK8 that can hold null). Below are examples demonstrating the first two reduce methods:
Summing and finding maximum values in a regular collection:
public static void main(String[] args) throws Exception { List<Integer> list = Arrays.asList(1, 3, 2); // Sum Integer sum = list.stream().reduce(1, (x, y) -> x + y); // Result is 7, which is the sum of list elements plus 1 System.out.println(sum); // Alternative writing Integer sum2 = list.stream().reduce(1, Integer::sum); System.out.println(sum2);  // Result: 7 // Finding maximum Integer max = list.stream().reduce(6, (x, y) -> x > y ? x : y); System.out.println(max);  // Result: 6 // Alternative writing Integer max2 = list.stream().reduce(1, Integer::max); System.out.println(max2); // Result: 3 }
Summing and finding maximum values in an object collection:
public class MyTest { public static void main(String[] args) { List<Person> personList = new ArrayList<Person>(); personList.add(new Person("Tom", 8900)); personList.add(new Person("Jack", 7000)); personList.add(new Person("Lily", 9000));  // Sum // Expected result: Optional[24900] System.out.println(personList.stream().map(Person::getSalary).reduce(Integer::sum)); // Finding maximum - Method 1 Person person = personList.stream().reduce((p1, p2) -> p1.getSalary() > p2.getSalary() ? p1 : p2).get(); // Expected result: Lily:9000 System.out.println(person.getName() + ":" + person.getSalary()); // Finding maximum - Method 2 // Expected result: Optional[9000] System.out.println(personList.stream().map(Person::getSalary).reduce(Integer::max)); // Finding maximum - Method 3: System.out.println(personList.stream().max(Comparator.comparingInt(Person::getSalary)).get().getSalary()); } }

3.3.3 Collecting (collect)

The collect operation can accept various methods as parameters to gather elements from the stream.
public interface Stream<T> extends BaseStream<T, Stream<T>> { <R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner); <R, A> R collect(Collector<? super T, A, R> collector); }
Observing the above interface definition, we see that collect uses Collector as a parameter, which includes four different operations: supplier (initial constructor), accumulator (accumulator), combiner (combiner), and finisher (terminator). In fact, the Collectors class includes many built-in collection operations.

1. Averaging Series

The methods averagingDouble, averagingInt, and averagingLong handle the process similarly, returning the average of the stream, differing only in the type of the returned result.
public static void main(String[] args) { List<Person> personList = new ArrayList<Person>(); personList.add(new Person("Tom", 8900)); personList.add(new Person("Jack", 7000)); personList.add(new Person("Lily", 9000)); Double averageSalary = personList.stream().collect(Collectors.averagingDouble(Person::getSalary)); System.out.println(averageSalary);  // Result: 8300 }

2. Summarizing Series

The methods summarizingDouble, summarizingInt, and summarizingLong can return a statistical result map of the stream, differing in the value types in the result map: double, int, and long.
public static void main(String[] args) { List<Person> personList = new ArrayList<Person>(); personList.add(new Person("Tom", 8900)); personList.add(new Person("Jack", 7000)); personList.add(new Person("Lily", 9000)); DoubleSummaryStatistics collect = personList.stream().collect(Collectors.summarizingDouble(Person::getSalary)); System.out.println(collect); // Output result: // DoubleSummaryStatistics{count=3, sum=24900.000000, min=7000.000000, average=8300.000000, max=9000.000000} }

3. Joining

The joining method can concatenate elements from the stream into a string using a specific separator (or directly if none is provided).
public static void main(String[] args) { List<Person> personList = new ArrayList<Person>(); personList.add(new Person("Tom", 8900)); personList.add(new Person("Jack", 7000)); personList.add(new Person("Lily", 9000)); String names = personList.stream().map(p -> p.getName()).collect(Collectors.joining(",")); System.out.println(names); }

4. Reduce

Collectors includes a built-in reduce method that can perform custom reductions, as shown in the following example:
public static void main(String[] args) { List<Person> personList = new ArrayList<Person>(); personList.add(new Person("Tom", 8900)); personList.add(new Person("Jack", 7000)); personList.add(new Person("Lily", 9000)); Integer sumSalary = personList.stream().collect(Collectors.reducing(0, Person::getSalary, (i, j) -> i + j)); System.out.println(sumSalary);  // Result: 24900 // Optional<Integer> sumSalary2 = list.stream().map(Person::getSalary).reduce(Integer::sum); System.out.println(sumSalary2);  // Optional[24900] }

5. Grouping By

The groupingBy method can group elements from the stream according to rules, similar to the groupBy statement in MySQL.
public static void main(String[] args) { List<Person> personList = new ArrayList<Person>(); personList.add(new Person("Tom", 8900)); personList.add(new Person("Jack", 7000)); personList.add(new Person("Lily", 9000)); // Single-level grouping Map<String, List<Person>> group = personList.stream().collect(Collectors.groupingBy(Person::getName)); System.out.println(group); // Output result: {Tom=[mutest.Person@7cca494b], // Jack=[mutest.Person@7ba4f24f],Lily=[mutest.Person@3b9a45b3]} // Multi-level grouping: first group by name, then by salary: Map<String, Map<Integer, List<Person>>> group2 = personList.stream() .collect(Collectors.groupingBy(Person::getName, Collectors.groupingBy(Person::getSalary))); System.out.println(group2); // Output result: {Tom={8900=[mutest.Person@7cca494b]},Jack={7000=[mutest.Person@7ba4f24f]},Lily={9000=[mutest.Person@3b9a45b3]}} }

6. toList, toSet, toMap

Built-in methods like toList and others in Collectors can conveniently collect elements from the stream into desired collections, a very commonly used feature, typically used with filter, map, etc.
public static void main(String[] args) { List<Person> personList = new ArrayList<Person>(); personList.add(new Person("Tom", 8900)); personList.add(new Person("Jack", 7000)); personList.add(new Person("Lily", 9000)); personList.add(new Person("Lily", 5000)); // toList List<String> names = personList.stream().map(Person::getName).collect(Collectors.toList()); System.out.println(names); // toSet Set<String> names2 = personList.stream().map(Person::getName).collect(Collectors.toSet()); System.out.println(names2); // toMap Map<String, Person> personMap = personList.stream().collect(Collectors.toMap(Person::getName, p -> p)); System.out.println(personMap); }

3.4 Mapping (map)

In streams, map can transform elements of one stream according to certain mapping rules into another stream.

1. Data>>Data

public static void main(String[] args) { String[] strArr = { "abcd", "bcdd", "defde", "ftr" }; Arrays.stream(strArr).map(x -> x.toUpperCase()).forEach(System.out::println); // Expected result: ABCD  BCDD  DEFDE  FTR }

2. Object Collection>>Data

public static void main(String[] args) { // To save space, personList reuses the demonstration data's personList personList.stream().map(person -> person.getSalary()).forEach(System.out::println); // Expected result: ABCD  BCDD  DEFDE  FTR }

3. Object Collection>>Object Collection

public static void main(String[] args) { // To save space, personList reuses the demonstration data's personList List<Person> collect = personList.stream().map(person -> { person.setName(person.getName()); person.setSalary(person.getSalary() + 10000); return person; }).collect(Collectors.toList()); System.out.println(collect.get(0).getSalary()); System.out.println(personList.get(0).getSalary()); List<Person> collect2 = personList.stream().map(person -> { Person personNew = new Person(null, 0); personNew.setName(person.getName()); personNew.setSalary(person.getSalary() + 10000); return personNew; }).collect(Collectors.toList()); System.out.println(collect2.get(0).getSalary()); System.out.println(personList.get(0).getSalary()); // Expected result: // 1. 18900   18900, indicating this approach altered the original personList. // 2. 18900   8900, indicating this approach did not alter the original personList. }

3.5 Sorting (sorted)

The sorted method sorts the stream and produces a new stream, which is a type of intermediate operation. The sorted method can use natural ordering or a specific comparator.
Natural sorting:
public static void main(String[] args) { String[] strArr = { "abc", "m", "M", "bcd" }; System.out.println(Arrays.stream(strArr).sorted().collect(Collectors.toList())); // Expected result: [M, abc, bcd, m] }
Custom sorting:
public static void main(String[] args) { String[] strArr = { "ab", "bcdd", "defde", "ftr" }; // 1. Sort by length naturally, i.e., from shortest to longest Arrays.stream(strArr).sorted(Comparator.comparing(String::length)).forEach(System.out::println); // 2. Sort by length in reverse order, i.e., from longest to shortest Arrays.stream(strArr).sorted(Comparator.comparing(String::length).reversed()).forEach(System.out::println); // 3. Sort by first letter in reverse order Arrays.stream(strArr).sorted(Comparator.reverseOrder()).forEach(System.out::println); // 4. Sort by first letter naturally Arrays.stream(strArr).sorted(Comparator.naturalOrder()).forEach(System.out::println); // /** * thenComparing * First sort by first letter * Then by string length */ @Test public void testSorted3_(){ Arrays.stream(arr1).sorted(Comparator.comparing(this::com1).thenComparing(String::length)).forEach(System.out::println); } public char com1(String x){ return x.charAt(0); } // Expected results: // 1. ftr  bcdd  defde // 2. defde  bcdd  ftr  ab // 3. ftr  defde  bcdd  ab // 4. ab  bcdd  defde  ftr }

3.6 Extracting Streams and Combining Streams

public static void main(String[] args) { String[] arr1 = {"a","b","c","d"}; String[] arr2 = {"d","e","f","g"}; String[] arr3 = {"i","j","k","l"}; /** * Streams can be combined into one stream (the combined stream type must be the same), only two can be combined at a time * Expected result: a b c d e (to save space, space replaces newline) */ Stream<String> stream1 = Stream.of(arr1); Stream<String> stream2 = Stream.of(arr2); Stream.concat(stream1,stream2).distinct().forEach(System.out::println);  /** * limit, limits the number of data obtained from the stream to the first n * Expected result: 1 3 5 7 9 11 13 15 17 19 */ Stream.iterate(1,x->x+2).limit(10).forEach(System.out::println);  /** * skip, skips the first n data * Expected result: 3 5 7 9 11 */ Stream.iterate(1,x->x+2).skip(1).limit(5).forEach(System.out::println); }
<END>

Recommended Reading:

【195期】Are You Familiar with the Conditional Judgment Functions CASE WHEN, IF, IFNULL in MySQL?

【194期】Redis—How Do We Execute Commands Not Wrapped in Third-Party Jars?

【193期】How to Use the Decorator Pattern to Extend Functionality Without Changing the Original Object?

5T Technical Resources Giveaway! Including but not limited to: C/C++, Linux, Python, Java, PHP, Artificial Intelligence, Microcontrollers, Raspberry Pi, etc. Reply “2048” in the public account to get it for free!!

A Comprehensive Guide to Java 8 Stream Features

Scan the QR code to follow my public account

I have read A Comprehensive Guide to Java 8 Stream Features

Leave a Comment

×