Detailed explanation of Java streams (Stream, Optional)

1. Java8 support for streams

A stream is a sequence of elements that is not tied to any particular storage mechanism. Unlike traversing elements in a collection, when using streams, we extract elements from a pipeline and perform operations on them. These pipes are usually connected in series to form an operation pipeline on the stream.

A core advantage of streams is that they make our programs smaller and easier to understand. Lambda expressions and method references come into their own when used with streams. Streams greatly increase the appeal of Java 8.

Suppose we want to display randomly selected non-repeating integers in the range of 5 to 20 in an orderly manner. With the help of streams, we only need to explain what we want to do:

package stream;

import java.util.Random;

public class Randoms {
    
    
    public static void main(String[] args) {
    
    
        new Random(47)
            .ints(5, 20)
            .distinct().limit(5)
            .sorted()
            .forEach(System.out::println);

        /*
         * 6
         * 10
         * 13
         * 16
         * 18
         */
    }
}

We first set a random seed for theRandom object. The ints() method will generate a stream. This method has multiple overloaded versions, two of which Versions of the parameter can set upper and lower bounds on the generated values. Use intermediate flow operation distinct() to remove duplicate values, and then use limit() to select the first 5 values, sorted() means the elements are ordered , and finally we want to display each entry, so we use forEach(), which will perform an operation on each stream object based on the function we pass. Here we pass a method reference System.out::println to display each entry on the console.

We do not see any explicit iteration mechanism in the code implemented using streams, so it is calledinternal iteration, which is stream programming. A core feature.

2. Creation of streams

You can easily turn a set of items into a stream using Stream.of():

package stream;

import java.util.stream.Stream;

public class StreamOf {
    
    
    public static void main(String[] args) {
    
    
        Stream.of(4, 1, 7).forEach(System.out::println);
        Stream.of("Hello ", "World ", "AsanoSaki").forEach(System.out::print);

        /*
         * 4
         * 1
         * 7
         * Hello World AsanoSaki
         */
    }
}

In addition, each Collection can use the stream() method to generate a stream:

package stream;

import java.util.*;
import java.util.stream.Collectors;

class A {
    
    
    int x;

    A(int x) {
    
     this.x = x; }
}

public class CollectionToStream {
    
    
    public static void main(String[] args) {
    
    
        List<A> listA = Arrays.asList(new A(1), new A(2), new A(3));
        System.out.println(listA.stream().mapToInt(a -> a.x).sum());

        Set<String> st = new TreeSet<>(Arrays.asList("Hello world and java".split(" ")));
        System.out.println(st.stream().map(String::toUpperCase).collect(Collectors.joining(" ")));

        Map<String, Double> mp = new HashMap<>();
        mp.put("PI", 3.14);
        mp.put("E", 2.718);
        mp.put("PHI", 1.618);
        mp.entrySet().stream()
            .map(e -> e.getKey() + ": " + e.getValue())
            .forEach(System.out::println);

        /*
         * 6
         * HELLO AND JAVA WORLD
         * PHI: 1.618
         * E: 2.718
         * PI: 3.14
         */
    }
}

After creating a List<A>, you only need to call stream(), a method that all collection classes have. The middle map() operation accepts each element in the stream, applies an operation on it to create a new element, and then passes the new element along the stream. Here . and . There are similarly named operations for containing mapToInt() Converts an object stream into an IntegerIntStreamFloatDouble

collect()The operation combines all the stream elements according to its parameters. When we use Collectors.joining(), the result is a String for each element. They will all be separated by joining() parameters, and there are many others Collectors that can generate different results.

To generate a stream from the Map collection, we first call entrySet() to generate a stream of objects, where each object contains a key and its associated values, and then separate them using getKey() and getValue().

2.1 Random number stream

RamdomClasses introduced streams in Java 8, with a set of methods that can generate streams:

package stream;

import java.util.Random;
import java.util.stream.Stream;

public class RandomGenerators {
    
    
    public static <T> void show(Stream<T> stream) {
    
    
        stream.limit(3).map(x -> x + " ").forEach(System.out::print);
        System.out.println();
    }

    public static void main(String[] args) {
    
    
        Random rand = new Random(47);
        show(rand.ints().boxed());
        show(rand.doubles().boxed());
        show(rand.ints(10, 20).boxed());  // 设置上下边界
        show(rand.ints(2).boxed());  // 设置流的大小
        show(rand.ints(2, 10, 20).boxed());  // 设置流的大小与上下边界

        /*
         * -1172028779 1717241110 -2014573909
         * 0.053412216308810656 0.5779976127815049 0.4170137422770571
         * 17 18 18
         * 1122537102 491149179
         * 19 18
         */
    }
}

To eliminate redundant code, the above example createsgenericmethodshow(Stream<T> stream). This feature is Will talk about it later. The type parameter T can be anything, so using Integer, Long and Double will work. However, the Random class will only generate values ​​of the basic types int, long, and double. Fortunately, boxed() stream operations automatically convert the base type to its corresponding wrapper type, allowing show() to accept the stream.

2.2 Interval range of integer type

IntStreamThe class provides a range() method that can generate a stream (a sequence composed of int values), which is very convenient when writing loops: a>

package stream;

import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class IntStreamRange {
    
    
    public static void main(String[] args) {
    
    
        // for-in循环
        for (int i: IntStream.range(1, 5).toArray())
            System.out.print(i + " ");  // 1 2 3 4
        System.out.println();

        // Stream
        System.out.println(IntStream.range(1, 5).boxed().map(Object::toString).collect(Collectors.joining(" ")));  // 1 2 3 4
        IntStream.range(1, 5).boxed().forEach(x -> System.out.print(x + " "));  // 1 2 3 4
        System.out.println();
    }
}

Now we write a repeat() utility function to replace the simple for loop:

package stream;

import java.util.stream.IntStream;

public class Repeat {
    
    
    static void repeat(int n, Runnable action) {
    
    
        IntStream.range(0, n).forEach(i -> action.run());
    }

    static void hello() {
    
    
        System.out.println("Hello");
    }

    public static void main(String[] args) {
    
    
        repeat(3, () -> System.out.println("Lambda"));
        repeat(2, Repeat::hello);

        /*
         * Lambda
         * Lambda
         * Lambda
         * Hello
         * Hello
         */
    }
}

2.3 Stream.generate()

Stream.generate() can accept any Supplier<T> (interface in java.util.function) and generate an object composed of T type of flow. If you want to create a stream consisting of the exact same objects, just pass a Lambda expression that generates these objects to generate():

package stream;

import java.util.Random;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Generator implements Supplier<String> {
    
    
    Random rand = new Random(47);
    char[] letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();

    @Override
    public String get() {
    
    
        return String.valueOf(letters[rand.nextInt(letters.length)]);
    }

    public static void main(String[] args) {
    
    
        String str = Stream.generate(new Generator()).limit(10).collect(Collectors.joining());
        System.out.println(str);  // YNZBRNYGCF

        Stream.generate(() -> "AsanoSaki").limit(2).forEach(s -> System.out.print(s + " "));  // AsanoSaki AsanoSaki
    }
}

2.4 Stream.iterate()

Stream.iterate() Starts with a seed (the first parameter) and passes it to the method referenced by the second parameter. The result is added to the stream and saved for next time< a i=1> The first parameter of the call, and so on. We can generate a previously implemented Fibonacci sequence by iteration:iterate()

package stream;

import java.util.stream.Stream;

public class StreamFibonacci {
    
    
    int x = 1;

    Stream<Integer> fib() {
    
    
        return Stream.iterate(0, i -> {
    
    
            int res = x + i;
            x = i;
            return res;
        });
    }

    public static void main(String[] args) {
    
    
        new StreamFibonacci().fib().limit(10).forEach(x -> System.out.print(x + " "));
        System.out.println();
        new StreamFibonacci().fib().skip(10).limit(10).forEach(x -> System.out.print(x + " "));

        /*
         * 0 1 1 2 3 5 8 13 21 34
         * 55 89 144 233 377 610 987 1597 2584 4181
         */
    }
}

iterate() will only remember the result, so you must use x to remember another element. We used the skip() operation, which has not been introduced before. It will directly discard the corresponding number of stream elements specified by its parameters. The first 10 are discarded here.

2.5 Stream generator

InBuilder (Builder) design pattern, we create a generator object, provide it with multiple pieces of construction information, and finally execute< /span>, assuming we implement reading each line of the file and converting it into a word stream: library provides such a (build) action. The GenerateStreamBuilder

package stream;

import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Stream;

public class FileToWordsBuilder {
    
    
    Stream.Builder<String> builder = Stream.builder();

    public FileToWordsBuilder(String filePath) throws Exception {
    
    
        Files.lines(Paths.get(filePath)).forEach(line -> {
    
    
            for (String s: line.split("[ .?,!]+"))
                builder.add(s);
        });
    }

    public static void main(String[] args) throws Exception {
    
    
        String filePath = "src/file/FileToWordsBuilder.txt";
        new FileToWordsBuilder(filePath).builder.build()
            .forEach(s -> System.out.print(s + " "));

        /*
         * Hello world Today is Saturday Have a nice day
         */
    }
}

The text content is as follows:

Hello world!
Today is Saturday?
Have a nice day!

The constructor adds all the words in the file, but it does not call build(), which means that as long as build() is not called, you can continue to a>Builder Add words to the object. If you try to add a word to after calling build(), an exception will be thrown. Stream.Builder

2.6 Arrays

ArraysThe class contains a static method named stream() that converts an array into a stream. Can be any object array, or the int, long and double primitive types, yielding IntStream, LongStream and DoubleStream:

package stream;

import java.util.Arrays;

public class ArraysStream {
    
    
    public static void main(String[] args) {
    
    
        Arrays.stream(new String[] {
    
     "ABC", "XYZ", "YYJ" })
            .forEach(s -> System.out.print(s + " "));
        System.out.println();

        Arrays.stream(new int[] {
    
     1, 2, 3 })
            .forEach(x -> System.out.printf("%d ", x));
        System.out.println();

        Arrays.stream(new double[] {
    
     3.14159, 2.718, 1.618 })
            .forEach(x -> System.out.printf("%.2f ", x));
        System.out.println();

        Arrays.stream(new int[] {
    
     9, 8, 7, 6, 5 }, 1, 4)
            .forEach(x -> System.out.printf("%d ", x));
        System.out.println();

        /*
         * ABC XYZ YYJ
         * 1 2 3
         * 3.14 2.72 1.62
         * 8 7 6
         */
    }
}

The last callstream() used two additional parameters. The first one indicates where in the array to start selecting elements, and the second one indicates the stopping position (open interval), That is, in this example, select the element [1, 4) in the array.

3. Intermediate operations

3.1 Sorting stream elements

Usesorted() for default sorting (from small to large). This method can also accept a Comparator parameter, which can be passed in a Lambda expression. You can also use the predefined Comparator:

package stream;

import java.util.Comparator;
import java.util.stream.Stream;

public class StreamSorted {
    
    
    public static void main(String[] args) {
    
    
        Stream.of(1, 2, 3)
            .sorted(Comparator.reverseOrder())
            .forEach(x -> System.out.print(x + " "));

        /*
         * 3 2 1
         */
    }
}

3.2 Remove elements

  • distinct(): Remove duplicate elements from the stream.
  • filter(Predicate): Filter only retains elements that meet specific conditions, that is, stream elements that satisfy the filter function Predicate to true.

Let’s look at an example of filtering prime numbers:

package stream;

import java.util.stream.LongStream;

public class Prime {
    
    
    static boolean isPrime(long n) {
    
    
        return LongStream.rangeClosed(2, (long)Math.sqrt(n)).noneMatch(i -> n % i == 0);
    }

    static LongStream getPrime() {
    
    
        return LongStream.iterate(2, i -> i + 1).filter(Prime::isPrime);
    }

    public static void main(String[] args) {
    
    
        Prime.getPrime().limit(10).forEach(x -> System.out.printf("%d ", x));
        System.out.println();

        /*
         * 2 3 5 7 11 13 17 19 23 29
         */
    }
}

rangeClosed() contains the upper bound value. If the result of any remainder operation on the elements in the stream is 0, then the noneMatch() operation returns true, if If any calculation result is equal to 0, returnfalse. noneMatch() will exit after the first failure without trying all subsequent calculations.

4. Optional type

Before studying the finalization operation, we must consider a question: What will happen if we request an object from the stream, but there is nothing in the stream? Is there some kind of object we can use that can both serve as a placeholder for a stream element and also notify us nicely (that is, without throwing an exception) when the element we're looking for doesn't exist?

This idea is implemented as Optional type. Some standard stream operations will return Optional objects because they cannot guarantee that the desired result will exist. These stream operations are listed below:

  • findFirst(): Returns Optional containing the first element. If this stream is empty, returns Optional.empty.
  • findAny(): Returns Optional containing any element. If this stream is empty, returns Optional.empty.
  • max() and min() return Optional containing the maximum or minimum value in the stream respectively. If the stream is empty, return Optional.empty.
  • reduce()A version of that does not take a identity object as its first argument (in other versions of reduce(), identity object will become the default result, so there is no risk of a null result), which will wrap the return value in a Optional.
  • For numeric streamsIntStream, LongStream, and DoubleStream, the average() operation Wrap the result in a Optional in case the stream is empty.

Take a look at the following code sample:

package stream;

import java.util.Optional;
import java.util.stream.Stream;

public class StreamOptional {
    
    
    static void test(Optional<String> stringOptional) {
    
    
        if (stringOptional.isPresent())
            System.out.println(stringOptional.get());
        else
            System.out.println("Nothing in Optional!");
    }

    public static void main(String[] args) {
    
    
        System.out.println(Stream.<String>empty().findFirst());
        test(Stream.of("Hello").findFirst());
        test(Stream.<String>empty().findFirst());

        /*
         * Optional.empty
         * Hello
         * Nothing in Optional!
         */
    }
}

At this time, an exception will not be thrown because the stream is empty, but a Optional.empty object will be obtained. Optional There is a toString() method that displays useful information.

Note that the empty stream is created by Stream.<String>empty(). If only Stream.empty() is used without any context information, then Java does not know what it should be. type, and this syntax solves that problem. If the compiler has enough contextual information, it can infer the type of the empty() call, like this:

Stream<String> s = Stream.empty();

When we receive a Optional, we must first call isPresent() to see if there is anything in it. If so, use it again get() to get.

4.1 Convenience functions

There are a number of convenience functions that can be used to obtain the data in Optional, which simplify the above process of first checking and then processing the contained object:

  • ifPresent(Consumer): If the object exists, use this object to call Consumer, otherwise do nothing.
  • orElse(otherObject): If the object exists, return this object, otherwise return otherObject.
  • orElseGet(Supplier): If the object exists, return this object, otherwise return the replacement object created using the Supplier function .
  • orElseThrow(Supplier): If the object exists, return this object, otherwise throw an exception created using the Supplier function.
package stream;

import java.util.Optional;
import java.util.stream.Stream;

public class OptionalFunctions {
    
    
    static Optional<String> emptyOptional = Stream.<String>empty().findFirst();
    static Optional<String> stringOptional = Stream.of("Hello").findFirst();

    public static void main(String[] args) {
    
    
        System.out.println("---------- ifPresent ----------");
        emptyOptional.ifPresent(System.out::println);
        stringOptional.ifPresent(System.out::println);

        System.out.println("---------- orElse ----------");
        System.out.println(emptyOptional.orElse("Other String"));
        System.out.println(stringOptional.orElse("Other String"));

        System.out.println("---------- orElseGet ----------");
        System.out.println(emptyOptional.orElseGet(() -> "Other Supplier Object"));
        System.out.println(stringOptional.orElseGet(() -> "Other Supplier Object"));

        System.out.println("---------- orElseThrow ----------");
        try {
    
    
            System.out.println(stringOptional.orElseThrow(() -> new Exception("Supplier Exception")));  // 先执行非空Optional
            System.out.println(emptyOptional.orElseThrow(() -> new Exception("Supplier Exception")));
        } catch (Exception e) {
    
    
            System.out.println("Caught" + e);
        }

        /*
         * ---------- ifPresent ----------
         * Hello
         * ---------- orElse ----------
         * Other String
         * Hello
         * ---------- orElseGet ----------
         * Other Supplier Object
         * Hello
         * ---------- orElseThrow ----------
         * Hello
         * Caughtjava.lang.Exception: Supplier Exception
         */
    }
}

4.2 Create Optional

When you need to write the code to generate Optional yourself, there are three static methods that can be used:

  • empty(): Returns an empty Optional.
  • of(value): If you already know that this value is not null, you can use this method to wrap it in a Optional .
  • ofNullable(value): If you don’t know whether this value is null, you can use this method, if value is . in a , otherwise it will wrap this null, it will automatically return Optional.emptyvalueOptional

Let’s take a look at an example:

package stream;

import java.util.Optional;

public class CreatingOptionals {
    
    
    static Optional<String>
        stringOptional = Optional.of("Hello"),
        emptyOptional = Optional.empty(),
        nullOptional = Optional.ofNullable(null);

    public static void main(String[] args) {
    
    
        System.out.println(stringOptional.orElse("Empty"));
        System.out.println(emptyOptional.orElse("Empty"));
        System.out.println(nullOptional.orElse("Empty"));
    }

    /*
     * Hello
     * Empty
     * Empty
     */
}

4.3 Operations on Optional objects

There are three ways to post-process Optional, so if your streaming pipeline generates a Optional, you can do one more at the end Processing:

  • filter(Predicate): Applies Predicate to the contents of Optional and returns its result. If Optional does not match Predicate, convert it to empty. If Optional itself is already empty, it will be returned directly.
  • map(Function): If Optional is not empty, apply Function to Optional The object contained in and returns the result, otherwise Optional.empty is returned.
  • flatMap(Function): Similar to map(), but the provided mapping function wraps the result in Optional so that flatMap() In the end, there won’t be any packaging.

These operations are not provided on numeric Optional.

Let’s take a look at the usage of filter:

package stream;

import java.util.Arrays;

public class OptionalFilter {
    
    
    static String[] elements = {
    
     "Dog", "Cat", "", "Bird" };

    public static void main(String[] args) {
    
    
        for (int i = 0; i <= elements.length; i++)
            System.out.println(
                Arrays.stream(elements)
                    .skip(i)
                    .findFirst()
                    .filter(s -> s.length() == 3)
            );

        /*
         * Optional[Dog]
         * Optional[Cat]
         * Optional.empty
         * Optional.empty
         * Optional.empty
         */
    }
}

Although the output looks like a stream, each time it enters the for loop, it will reacquire a stream and skip the loop with for The index sets the number of elements, which makes it look like consecutive elements in the stream, and then it does findFirst(), getting the first of the remaining elements, which will be wrapped in a < Returned in /span>Optional.

Note that our for loop loops to i == elements.length, so the last element will exceed the stream. But this will automatically change to Optional.empty.

4.4 Stream composed of Optional

Suppose there is a generator that may generate null values. If a stream is created using this generator, we naturally want to wrap these elements in Optional, when using this stream, we have to figure out how to get the object in Optional:

package stream;

import java.util.Optional;
import java.util.Random;
import java.util.stream.Stream;

class Signal {
    
    
    private final String msg;
    static Random rand = new Random(47);

    Signal(String msg) {
    
     this.msg = msg; }

    @Override
    public String toString() {
    
    
        return "Signal(" + msg + ")";
    }

    public static Signal getSignal() {
    
    
        switch (rand.nextInt(4)) {
    
    
            case 1: return new Signal("Case 1");
            case 2: return new Signal("Case 2");
            default: return null;
        }
    }

    public static Stream<Optional<Signal>> getSignalOptStream() {
    
    
        return Stream.generate(Signal::getSignal).map(Optional::ofNullable);
    }
}

public class StreamOfOptionals {
    
    
    public static void main(String[] args) {
    
    
        Signal.getSignalOptStream().limit(5).forEach(System.out::println);
        System.out.println("--------------------");
        Signal.getSignalOptStream().limit(5)
            .filter(Optional::isPresent)
            .map(Optional::get)
            .forEach(System.out::println);

        /*
         * Optional[Signal(Case 2)]
         * Optional[Signal(Case 1)]
         * Optional[Signal(Case 2)]
         * Optional.empty
         * Optional.empty
         * --------------------
         * Signal(Case 2)
         * Signal(Case 1)
         * Signal(Case 2)
         * Signal(Case 2)
         */
    }
}

Here I use only that is not empty, and then call =4> to get the object wrapped in it, because each case requires us to decide what "no value" means, so we usually need to take a different approach for each application. Optionalmap()get()

5. Termination operation

These operations take a stream and produce a final result; they do not send anything to a backend stream. Therefore, finalization is always the last thing we can do in a pipeline.

5.1 Convert a stream to an array

  • toArray(): Convert stream elements into an array of appropriate type.
  • toArray(generator):generator is used to allocate its own array storage in specific situations.

Let’s look at the example directly:

package stream;

import java.util.Arrays;
import java.util.Random;

public class RandInts {
    
    
    public static void main(String[] args) {
    
    
        int[] nums = new Random(47).ints(0, 10).limit(10).toArray();
        System.out.println(Arrays.toString(nums));  // [8, 5, 3, 1, 1, 9, 8, 0, 2, 7]
    }
}

5.2 Apply a finalization operation to each stream element

  • forEach(Consumer): We have seen this usage many times, using System.out::println as a Consumer function.
  • forEachOrdered(Consumer): This version ensures that forEach operates on elements in the order of the original stream.

The first form is explicitly designed to operate on elements in any order, which only makes sense when the parallel() operation is introduced. parallel() Let Java try to perform the operation on multiple processors. It can do this precisely because it uses streams, which it can split into multiple streams (usually one stream per processor) and run each stream on a different processor.

We introduce parallel() in the following example to understand the role and necessity of forEachOrdered():

package stream;

import java.util.Arrays;
import java.util.Random;

public class ForEachOrdered {
    
    
    public static void main(String[] args) {
    
    
        int[] nums = new Random(47).ints(0, 10).limit(10).toArray();  // 确保每次流元素都相同

        Arrays.stream(nums).forEach(x -> System.out.printf("%d ", x));
        System.out.println();

        Arrays.stream(nums).parallel().forEach(x -> System.out.printf("%d ", x));
        System.out.println();

        Arrays.stream(nums).parallel().forEachOrdered(x -> System.out.printf("%d ", x));
        System.out.println();

        /*
         * 8 5 3 1 1 9 8 0 2 7
         * 8 9 0 7 2 3 5 1 8 1
         * 8 5 3 1 1 9 8 0 2 7
         */
    }
}

In the first stream, we did not use parallel(), so the results are displayed in the order they appear from Arrays.stream(nums). The second stream introduces parallel(). Even for such a small stream, we can see that the order of output is different from before. This is because there are multiple processors processing this problem, and if you run this program multiple times, you will find that the output will be different each time. This is due to the uncertainty caused by multiple processors processing this problem at the same time. factor.

The last stream still uses parallel(), but uses forEachOrdered() to force the results back to the original order. Therefore, using for streams other than parallel() will have no effect. forEachOrdered()

5.3 Collection operations

  • collect(Collector): Use this Collector to accumulate stream elements into a result set.
  • collect(Supplier, BiConsumer, BiConsumer): Similar to above, but Supplier will create a new result set, the first BiConsumer is used to include the next element into the result function in , the second oneBiConsumer is used to combine two values.

We have seen only a few examples of Collectors objects before, and we can collect stream elements into any particular kind of collection. Suppose we want to end up with our items in a TreeSet so that they are always ordered. There is no specific method in Collectors, only , but it is possible to use , and Pass it a constructor reference of any type. The following program extracts words from the file and puts them into :toTreeSet()toSet()Collectors.toCollection()CollectionTreeSet

package stream;

import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;

public class TreeSetOfWords {
    
    
    public static void main(String[] args) throws Exception {
    
    
        String filePath = "src/file/FileToWordsBuilder.txt";
        Set<String> words = Files.lines(Paths.get(filePath))
            .flatMap(line -> Arrays.stream(line.split("\\W+")))
            .filter(word -> !word.matches("\\d+"))
            .map(String::trim)
            .collect(Collectors.toCollection(TreeSet::new));
        System.out.println(words);

        /*
         * [Have, Hello, Saturday, This, Today, a, day, digit, is, nice, world]
         */
    }
}

Among them FileToWordsBuilder.txt Sentence content below:

Hello world!
Today is Saturday?
Have a nice day!
This is digit 666.

Files.lines() opens the file pointed to by Path and turns it into a line of text Stream. The next line of code splits these lines of text by one or more non-word characters (\\W+), where the resulting array becomes Arrays.stream() a>. to remove any surrounding whitespace that may be present, and finally place the words into a that are all numbers. Next use will find and delete composed of words. Stream, and then the result is expanded and mapped back to a Streammatches(\\d+)StringString.trim()TreeSet

5.4 Combine all flow elements

  • reduce(BinaryOperator): Use BinaryOperator to combine all stream elements. Because this stream may be empty, a Optional is returned.
  • reduce(identity, BinaryOperator): Same as above, but using identity as the initial value for this combination, so even if the stream is empty, we still get identity as a result.
  • reduce(identity, BiFunction, BinaryOperator): This one is more complex (so we won't cover it), but it's listed here because it may be more efficient. This requirement can be expressed more simply by combining explicit map() and reduce() operations.

Let’s take a look at the simplest usage:

package stream;

import java.util.Random;
import java.util.stream.Stream;

class Apple {
    
    
    private final int price;

    Apple(int price) {
    
     this.price = price; }

    int getPrice() {
    
     return price; }

    @Override
    public String toString() {
    
    
        return "Apple(" + price + ")";
    }

    // 生成器
    static Random rand = new Random(47);
    static Apple generator() {
    
    
        return new Apple(rand.nextInt(100));
    }
}

public class Reduce {
    
    
    public static void main(String[] args) {
    
    
        Stream.generate(Apple::generator)
            .limit(5)
            .peek(a -> System.out.println("Peek: " + a))
            .reduce((a0, a1) -> a0.getPrice() > 60 ? a0 : a1)
            .ifPresent(System.out::println);

        /*
         * Peek: Apple(58)
         * Peek: Apple(55)
         * Peek: Apple(93)
         * Peek: Apple(61)
         * Peek: Apple(61)
         * Apple(93)
         */
    }
}

When we use reduce(), we do not provide the first parameter as the initial value, which means Then it will generate a Optional, and only when the result is not empty, the Optional.ifPresent() method will call Consumer<Apple>(The reason why System.out::println is consistent is that it can convert into a < through the toString() method a i=11>). AppleString

The first parameter in the Lambda expressiona0 is the result returned when this reduce() was called last time, and the second parameter, which is the sequence the next element in . , otherwise accept is greater than 60, accept 's a1 is the new value from the stream. If a0pricea0a1

As a result, what we get is the firstprice in the streamApple greater than 60. Once such an object is found, it will Hold on to it, even if other candidates appear.

Guess you like

Origin blog.csdn.net/m0_51755720/article/details/134087431