当前位置:网站首页>Stream programming: stream support, creation, intermediate operation and terminal operation

Stream programming: stream support, creation, intermediate operation and terminal operation

2022-06-21 09:35:00 tragically unhappy

Preface

Collections optimize the storage of objects , And flow (Streams) It is about the processing of a group of objects .

A stream is a sequence of elements independent of any particular storage mechanism —— actually , We say that streams have no storage .

  • Instead of iterating through a set (Iterator) Element approach , Use stream (Streams) It can be downloaded from Extract elements from the pipeline and manipulate them . These pipes are usually connected in series to form a complete set of pipelines , And the stream operates on them .
  • Most of the time , You store objects in collections to handle them , So when you program, you will find that your main focus will shift from collections to streams .
  • One of the core benefits of flow is : It makes your program shorter and easier to understand . When will Lambda When expressions and method references are used with streams, you will find that they are self-contained . Flow makes Java 8 Attraction of machines and tools ( It's really great ).

for instance , If you want to show it randomly 5-20 Between non repeating integers (4 Conditions ).
Your idea may have been like this at first , First focus on which ordered set to use , Then, follow-up operations are carried out around this set . But using streaming programming , You can do that :

public class Randoms {
    
    public static void main(String[] args) {
    
        new Random(47)
                .ints(5, 20)
                .distinct()
                .limit(7)
                .sorted()
                .forEach(System.out::println);
    }
}
/* output: 6 10 13 16 17 18 19 */

This is really too simple , We just need to simply state what we want to do .
First we give Random Object a seed value , ints() Method generates a stream and ints() Methods can be overloaded in many ways —– Two parameters define the boundary of the resulting value . This will produce a stream of random integers . Then we use the stream Intermediate operation distinct() Make the elements in the flow not repeated , then limit() Take the first seven elements . Then sort and iterate through the output ,forEach() The operation is performed on each object in the stream according to the function passed to it .
Be careful : No variables are declared in the above example . Streams can be used without assignment or mutable data , Modeling existing systems , This is very useful .
What we did before was Command programming In the form of ( Indicate how to do each step ), And now we use Declarative programming (Declarative Programming)—— It states what to do , Instead of pointing out how to do .

public class ImperativeRandoms {
    
    public static void main(String[] args) {
    
        Random rd = new Random(47);
        SortedSet<Integer> rints = new TreeSet<>();
        while (rints.size() < 7) {
    
            int r = rd.nextInt(20);
            if (r < 5) {
    
            // If the conditions are met , Just jump out of this cycle 
                continue;
            }
            rints.add(r);
        }
        System.out.println(rints);
    }
}
/* output: [7, 8, 9, 11, 15, 16, 18] */

We should clearly see the difference between the two codes . stay Randoms.java in , We don't have to define any variables , But in this example we define 3 individual , And the code is more complex , And nextInt() Method has no lower bound , The efficiency is not high .
most important of all : You have to study the code to see what this example is doing , And in the Random.java in , The code will tell you what it is doing .
Like in ImperativeRandoms.java The way in which the iteration process is written as shown in is called External iteration . And in the Random.java Is an internal iteration , This is a The core features of streaming programming . Internal iterations can produce more readable code , And it's easier to use multi-core processors . By giving up control of the iterative process , We can give control to parallel The mechanism of Urbanization .
Another important aspect : Streams are lazy loaded . This means that it will only be calculated when absolutely necessary . You can think of flow as “ Delay list ”, Because of the computation delay , Flow enables us to express very large ( Even unlimited ) Sequence , Without thinking about memory .

One 、 Stream support

Java Designers face such a difficult problem : How to integrate a new concept of flow into the existing class library ? By adding more methods to the previous class library , As long as we don't change the original method , Existing code will not be disturbed .
The second problem is the use of Interface Class library of . If you add a new method to the interface , Then all the class libraries that implement this interface have to be changed .Java 8 The solution is : Add the interface default The method of decoration . So you will find some in some stream interfaces default Method , This method does not require subclasses to be implemented separately , Its preset operation has met almost all our usual needs . Through this program , Designers can smoothly embed streaming methods into existing classes .
There are three types of stream operations : Create stream , Modify stream elements ( Intermediate operation ), Consumption stream elements ( Terminal operation ). The last type usually means collecting stream elements ( Import the finished stream into a collection ).

Two 、 Stream creation

public class StreamOf {
    
    public static void main(String[] args) {
    
        Stream.of(new Bubble(1), new Bubble(2), new Bubble(3))
                .forEach(System.out::println);
        Stream.of("It's ", "a ", "wonderful ", "day ", "for ", "pie!")
                .forEach(System.out::print);
        System.out.println();
        Stream.of(3.1415926, 2.718, 1.618)
                .forEach(System.out::println);
        System.out.println("=====================================");

        List<Bubble> bubbles = Arrays.asList(new Bubble(3), new Bubble(4), new Bubble(5));
        System.out.println(bubbles.stream()
                .mapToInt(b -> b.i)
                .sum());
        Set<String> w = new HashSet<>(Arrays.asList("It's a wonderful day for pie!".split(" ")));
        w.stream()
                .map(x -> x + " ")
                .forEach(System.out::print);
        System.out.println();
        Map<String, Double> m = new HashMap<>();
        m.put("pi", 3.14159);
        m.put("e", 2.718);
        m.put("phi", 1.618);
        m.entrySet()
        .stream()
        .map(e->e.getKey() + ":" + e.getValue())
        .forEach(System.out::println);
    }
}
/* output: Bubble{i=1} Bubble{i=2} Bubble{i=3} It's a wonderful day for pie! 3.1415926 2.718 1.618 ===================================== 12 a pie! It's for wonderful day phi:1.618 e:2.718 pi:3.14159 */

We can go through Stream.of() It's easy to turn a set of elements into a stream . besides , Each collection can be created by calling stream() Method to generate a stream .

  • Creating List<Bubble> After object , All we need to do is call the stream().
  • Intermediate operation map() Gets all the elements in the stream , And the element in the stream is operated to create a new element , Then it is transferred to the reflux . Usually map() Gets the object and creates a new object , But here comes a special For numeric types The flow of . for example :mapToInt() Method converts a stream of objects to a stream containing integer numbers IntStream.
  • In order to learn from Map Stream data is generated in the collection , We call entrySet() Generate an object stream , Each object contains a key Key and its associated value value . Then call... Separately getKey() and getValue().

2.1 Random number stream

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

    public static void main(String[] args) {
    
        Random rd = new Random(47);
        /*  Do not control the upper and lower limits , without show Methods limit, There will be infinite IntStream The value in .  here boxed Method has generated a stream before , It is only a basic data type ( Such as IntStream), and boxed  You can wrap basic data types (Stream<Integer>) */
        show(rd.ints().boxed());
        show(rd.longs().boxed());
        show(rd.doubles().boxed());
        // Control the upper and lower limits 
        show(rd.ints(10, 20).boxed());
        show(rd.longs(50, 100).boxed());
        show(rd.doubles(20, 30).boxed());
        // Control flow size 
        show(rd.ints(2).boxed());
        show(rd.longs(2).boxed());
        show(rd.doubles(2).boxed());
        // Control the size and limits of the flow 
        show(rd.ints(2, 10, 20).boxed());
        show(rd.longs(2, 50, 100).boxed());
        show(rd.doubles(2, 20, 30).boxed());
    }
}
/* output: -1172028779 1717241110 -2014573909 229403722 +++++++++++++++ 2955289354441303771 3476817843704654257 -8917117694134521474 4941259272818818752 +++++++++++++++ 0.2613610344283964 0.0508673570556899 0.8037155449603999 0.7620665811558285 +++++++++++++++ 16 10 11 12 +++++++++++++++ 65 99 54 58 +++++++++++++++ 29.86777681078574 24.83968447804611 20.09247112332014 24.046793846338723 +++++++++++++++ 1169976606 1947946283 +++++++++++++++ 2970202997824602425 -2325326920272830366 +++++++++++++++ 0.7024254510631527 0.6648552384607359 +++++++++++++++ 17 18 +++++++++++++++ 81 86 +++++++++++++++ 21.898377705316413 23.22662025293785 +++++++++++++++ */

To eliminate redundant code , Generic methods are used here to create different types of flows . Actually here Random class Only basic types of streams can be generated , however :boxed() The stream operation automatically wraps the base type into the corresponding boxing type , Thus making show() Method can call .
We can use Random Create... For any collection of objects Supplier.

public class RandomWords implements Supplier<String> {
    
    List<String> words = new ArrayList<>();
    Random rand = new Random(47);

    RandomWords(String fname) throws IOException {
    
        List<String> lines = Files.readAllLines(Paths.get(fname));
        // Skip the first line 
        for (String line : lines.subList(1, lines.size())) {
    
            for (String word : line.split("[ .?,]+")) {
    
                words.add(word);
            }
        }
    }

    @Override
    public String get() {
    
        return words.get(rand.nextInt(words.size()));
    }

    public static void main(String[] args) throws IOException {
    
        System.out.println(
                Stream.generate(new RandomWords("C:\\Users\\gt136\\Downloads\\Documents\\cheese.txt"))
                        .limit(10)
                        .collect(Collectors.joining(" ")));
    }
}
/* cheese.txt // streams/Cheese.dat Not much of a cheese shop really, is it? Finest in the district, sir. And what leads you to that conclusion? Well, it's so clean. It's certainly uncontaminated by cheese. output: it shop sir the much cheese by conclusion district is */
  • You can see it here split() More complex applications . In the constructor : Every line is split Methods are separated by spaces within square brackets or other punctuation marks . After square brackets + The sign means that something in front of it can appear once or more .
  • collect() The operation combines all the flow elements according to the parameters . When you use Collectors.joining() As its parameter , Will get a String Result of type : That is, all elements in the stream are joining() The parameters in are separated .
  • generate() Method can put any Supplier<T> Used to generate T Unordered flow of type , It's rewritten get() The method is collect Method to call .

2.2 int Type of range

public class Range {
    
    // Generate from start-end Step by step step A growing sequence 
    public static int[] range(int start, int end, int step) {
    
        if (step == 0) {
    
            throw new IllegalArgumentException("Step cannot be zero!");
        }
        // Operating range 
        int sz = Math.max(0,step >= 0 ? (end + step -1 -start) / step : (end + step + 1 -start) / step);
        int[] result = new int[sz];
        for (int i = 0; i < sz; i++) {
    
            result[i] = start + (i * step);
        }
        return result;
    }
    // Generate from start  To  end  In turn, increasing  1  Sequence 
    public static int[] range(int start, int end) {
    
        return range(start, end, 1);
    }
    // Generate from 0  To  n  In turn, increasing  1  Sequence 
    public static int[] range(int n) {
    
        return range(0, n);
    }
}

IntStream Class provides range() Method is used to generate an integer :

import static java.util.stream.IntStream.*;
public class Ranges {
    
    public static void repeat(int n, Runnable action) {
    
        range(0, n).forEach(i->action.run());
    }
    public static void main(String[] args) {
    
        // traditional method 
        int result = 0;
        for (int i = 10; i < 20; i++) {
    
            result += i;
        }
        System.out.println(result);
        //for-in  loop 
        result = 0;
        for (int i : range(10, 20).toArray()) {
    
            result += i;
        }
        System.out.println(result);
        // Use stream 
        System.out.println(range(10,20).sum());
        // Use stream but not applicable IntStream The method in 
        System.out.println(new Random()
                .ints(10, 20)
                .limit(5).sum());

        repeat(3,()-> System.out.println("Looping!"));
    }
}

The first way in the main method is that we traditionally write for The way of circulation ; The second way is to use range() Method ( The package was introduced in advance ) Create a stream and convert it to an array , And then in for-in Used in code blocks . But the third is to use streams entirely .
Be careful :IntStream.range() Compared with the previous Range.range() Subject to many restrictions . This is due to its optional third parameter , The latter allows steps greater than one ( The former is fixed 1), And can be generated from large to small .

2.3 generate() Method can put any Supplier<T> Used to generate T Unordered flow of type

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

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

    public static void main(String[] args) {
    
        String word = Stream.generate(new Generator())
                .limit(30)
                .collect(Collectors.joining());
        System.out.println(word);
        // Can produce  T  Object contains a stream of the same data ( Because there is only one value )
        Stream.generate(() -> "duplicate")
                .limit(3)
                .forEach(System.out::println);
                
        Stream.generate(Bubble::bubbler)
                .limit(5)
                .forEach(System.out::println);
    }
}
/* output: YNZBRNYGCFOWZNTCQRGSEGZMMJMROE duplicate duplicate duplicate Bubble{i=0} Bubble{i=1} Bubble{i=2} Bubble{i=3} Bubble{i=4} */

reference RandomWords.java Medium Stream.generate() collocation Supplier<T> Usage of , Remember that it can generate any T Types of flow , The same is collect Called get() Method .
Just to summarize :new Random().ints() Can produce a random sequence of values , and Stream.generate() Then a random sequence can be generated in the provided set or object .
If you want to create a stream that contains the same object , Just pass in a lambda To generate() in , Just like the last expression above .

2.4 iterate()

Stream.iterate() The first element of the resulting stream is the seed (iterate The first parameter of the method ), Then pass the seed to the method (iterate The second argument to the method ). The result of the method run is added to the stream ( As the next element of the stream ), And stored , As the next call iterate() The first parameter of the method , And so on . We can use it to generate a Fibonacci sequence .

public class Fibonacci {
    
    int x = 1;

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

    public static void main(String[] args) {
    
        new Fibonacci().number()
                .skip(20)// Before filtration 20 individual 
                .limit(10) // And then take 10 individual 
                .forEach(System.out::println);
    }
}
/* output: 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 */

3、 ... and 、 The builder mode of flow

stay Builder pattern (Builder design pattern) in , First ① Create a builder object , then ② Pass multiple pieces of information needed to create the flow to it , Last ③** builder Object to perform “ establish ” flow ** The operation of .Stream The library provides Builder, Here is the code to rewrite the above example :

public class FileToWordsBuilder {
    
    //builder Method returns a Builder Implementation class of 
    Stream.Builder<String> builder = Stream.builder();

    public FileToWordsBuilder(String filePath) throws IOException {
    
        Files.lines(Paths.get(filePath))
                .skip(1)// Skip the first line 
                .forEach(line->{
    
                    for (String w : line.split("[ .?,]+")) {
    
                        builder.add(w);
                    }
                });
    }
    Stream<String> stream() {
    
        return builder.build();
    }

    public static void main(String[] args) throws IOException {
    
        new FileToWordsBuilder("cheese.txt")
                .stream()
                .limit(7)
                .map(w -> w + " ")
                .forEach(System.out::print);
    }
}
/* output: Not much of a cheese shop really */

Be careful : The constructor adds all the words in the file ( Except for the first line ), however , No call builder(). As long as you don't call stream() Method , You can go on to builder Object .
In the more complete form of the class , You can add a flag bit to view build() Is it called , And if possible, add a way to add more words . stay Stream.builder call build() Method and continue to try to add words will produce an exception .

3.1 Arrays

Arrays Class contains a named stream() A static method for converting an array to a stream . The main method is used to create a stream , And will excute() Apply to each element .

public interface Operations {
    
  void execute();
  static void runOps(Operations... ops) {
    
    for(Operations op : ops)
      op.execute();
  }
  static void show(String msg) {
    
    System.out.println(msg);
  }
}
/*************************************/
public class Machine2 {
    
    public static void main(String[] args) {
    
        Arrays.stream(new Operations[]{
    
                () -> Operations.show("Bing"),
                () -> Operations.show("Crack"),
                () -> Operations.show("Twist"),
                () -> Operations.show("Pop")
        }).forEach(Operations::execute);
    }
}
/* output: Bing Crack Twist Pop */

new Operations[] Dynamically created Operations An array of objects .
stream() The same can produce IntStream, LongStream and DoubleStream.

public class ArrayStreams {
    
    public static void main(String[] args) {
    
        Arrays.stream(new double[]{
    3.14159, 2.718, 1.618 })
                .forEach(n->System.out.format("%f ", n));
        System.out.println();
        Arrays.stream(new int[]{
    1, 3, 5})
                .forEach(n -> System.out.format("%d ", n));
        System.out.println();
        Arrays.stream(new long[]{
    11, 22, 33, 44, 66})
                .forEach(n -> System.out.format("%d ", n));
        System.out.println();

        // Select a subdomain 
        Arrays.stream(new int[]{
    1, 3, 5, 7, 8, 10, 12}, 3, 6)
                .forEach(n -> System.out.format("%d ", n));
    }
}
/* output: 3.141590 2.718000 1.618000 1 3 5 11 22 33 44 66 7 8 10 */

the last one stream() The call to the method of has two additional parameters , The first parameter tells stream() Select the element from that position in the array , The second parameter is the stop position .

3.2 Regular expressions

Java 8 stay java.util.regex.Pattern A new method has been added to splitAsStream(). This method can convert a sequence of characters into a stream according to the formula passed in . But there's a limit : The input can only be CharSequence, So you can't take a stream as its parameter .

public class FileToWordsRegexp {
    
    private String all;

    public FileToWordsRegexp(String filepath) throws IOException {
    
        this.all = Files.lines(Paths.get(filepath))
                .skip(1)
                .collect(Collectors.joining(" "));
    }
    public Stream<String> stream() {
    
        return Pattern
                .compile("[ .,?]+")
                .splitAsStream(all);//splitAsStream()  receive calls only charSequence  object , however String  Meet this requirement 
    }

    public static void main(String[] args) throws IOException {
    
        FileToWordsRegexp fw = new FileToWordsRegexp("cheese.txt");
        fw.stream()
                .limit(7)
                .map(m -> m + " ")
                .forEach(System.out::print);
        fw.stream()
                .skip(7)
                .limit(2)
                .map(x -> x + " ")
                .forEach(System.out::print);
    }
}
/*output: Not much of a cheese shop really is it */

This time we use the stream to convert the file to a string , Then use regular expressions to turn the string into a stream of words .
Everything in the file is read in the constructor , When calling stream() When , You can get a stream as usual , but This time we can call stream(), Each time a new stream is created from the stored string . There is a limitation , The whole file must be stored in memory ; Many times this is not a problem , But this misses the important advantage of streaming operations :

  1. “ There is no need to store the stream .” Of course , Streams do require some memory storage , But only a small part of the sequence is stored , Not the whole sequence .
  2. They're lazy load calculations

We'll talk about solutions later . It's just 4.5

Four 、 Intermediate operation

Intermediate operations are used to get objects from a stream , And output the object as another stream from the back end , To connect to other places .

4.1 Tracking and debugging

peek() The purpose of the operation is to help debug . It allows you to view elements in the stream without modification .

public class Peeking {
    
    public static void main(String[] args) throws IOException {
    
        FileToWordsBuilder fw = new FileToWordsBuilder("C:\\Users\\gt136\\Downloads\\Documents\\cheese.txt");
        fw.stream()
                .skip(21)
                .limit(4)
                .map(w -> w + " ")
                .peek(System.out::print)
                .map(String::toUpperCase)
                .peek(System.out::print)
                .map(String::toLowerCase)
                .forEach(System.out::print);
    }
}
/* output: Well WELL well it's IT'S it's so SO so clean CLEAN clean */

At first, I was confused about the printing of this stream , If you step through the execution of this flow , You will find the characteristics of declarative programming : It is all executed according to the declaration , Finally, the flow you need is presented to you , And you can't see the middle operation .

4.2 Stream element ordering

		fw2.stream()
                .skip(10)
                .limit(10)
                .sorted(Comparator.reverseOrder())
                .map(w -> w + " ")
                .forEach(System.out::print);

We met before sorted() Method , But here is another form of implementation : Passed in a Comparator Parameters .

4.3 Remove elements

public class Prime {
    
    public static Boolean isPrime(long n) {
    
        return rangeClosed(2, (long) Math.sqrt(n))// Generate a sequence that increases by one from the first parameter to the second parameter , Contains the initial value and upper limit value 
                .noneMatch(i -> n % i == 0);
    }
    public static LongStream numbers() {
    
        return iterate(2, i -> i + 1)// Produce a result from  2  The beginning of an infinite sequence 
                .filter(Prime::isPrime);
    }

    public static void main(String[] args) {
    
        new Prime().numbers()
                .limit(10)
                .forEach(n -> System.out.format("%d ", n));
        System.out.println();
        new Prime().numbers()
                .skip(90)
                .limit(10)
                .forEach(n-> System.out.format("%d ",n));
    }
}
/* output: 2 3 5 7 11 13 17 19 23 29 467 479 487 491 499 503 509 521 523 541 */
  • distinct() : Can eliminate duplicate elements , Instead of creating a Set To deal with it , This method is much simpler .
  • filter(Predicate): Filtering operation , Keep the following elements : If the element is passed to the filter function, the result is true .

In the above code isPrime() Is used to detect prime numbers ,rangeClosed(long startInclusive, final long endInclusive) Contains the initial and upper values . This function means that if you can't divide by an integer , be noneMatch() return true, If 0, return false, And this method will exit in case of any error .

4.4 Apply functions to elements

  • map(Function): Apply function operations to the elements of the input stream , And pass the return value to the output stream .
  • mapToInt(ToIntFuntion): Ibid , But the result is IntStream .
  • mapToLong(ToLongFunction): Ibid , But the result is LongStream.
  • mapToDouble(ToDoubleFunction): Ibid , But the result is DoubleStream.
public class FunctionMap {
    
    static String[] elements = {
    "12", "", "23", "34"};
    // Convert an array to a stream 
    static Stream<String> testStream() {
    
        return Arrays.stream(elements);
    }

    static void test(String descr, Function<String, String> func) {
    
        System.out.println("---( " + descr + ")---");
        testStream()
                .map(func)
                .forEach(System.out::println);
    }

    public static void main(String[] args) {
    
        // Make the original data into the form we want 
        test("add brackets", s -> "[" + s + "]");
        test("Increment",s->{
    
            try {
    
                return Integer.parseInt(s) + 1 + "";
            } catch (NumberFormatException e) {
    
                return s;
            }
        });

    }
}
/* output: ---( add brackets)--- [12] [] [23] [34] ---( Increment)--- 13 24 35 */

In the self incrementing example above , We convert a string to an integer , If it cannot be converted to an integer, an exception will be thrown , At this point, put the original string into the output stream .
map() Mapping one string to another , however We can produce and accept completely different types , Thus changing the data type of the stream . You only need to process the single data in the stream , such as :.map(Numbered::new),Numbered The constructor of receives elements .

4.5 stay map() Combinatorial flow in water

Suppose you now have an incoming element stream , And we're going to use map() function , But the problem is , The function of these functions is to generate a stream : We want to create a flow of elements , In fact, there is a flow of element flow ! Please note that : The operation here is for the incoming Flow, not front The function type in the example

  • flatMap(): Did two things : Apply the stream generating function to each element ( And map() It's the same thing ), Then flatten each stream into elements , So what it ultimately produces is just elements .
  • flatMap(Function): When Function Use when generating streams .
  • flatMapToInt(Function): When Function produce IntStream When using .
  • flatMapToLong(Function): When Function produce LongStream When using .
  • flatMapToDouble(Function): When Function produce DoubleStream When using .

To figure out how it works , Pass a deliberately designed function from to map() Start .

/** *  Stream in stream   Among them  map  Method  :public final <R> Stream<R> map(Function<? super P_OUT, ? extends R> mapper) {} *  This map The parameter in is  Function: That is to say, it will call apply Method , This is realized by ourselves ; and  Function  Parameters in , The first one refers to the type of input stream , *  That is, if you are the type of flow  Integer  type , So this is  Integer  type ; The second parameter refers to the type of new stream you return , This can also be defined by itself . * @Date 2021/6/17 16:41 * @Created by gt136 */
public class StreamOfStreams {
    
    public static void main(String[] args) {
    
        Stream.of(1,2,3)
        		/*.map(new Function<Integer, Stream<String>>() { @Override public Stream<String> apply(Integer integer) { return Stream.of("null"); } })*/
                .map(i->Stream.of("Gonzo","Kermit","Breaker"))
                .map(e->e.getClass().getName())
                .forEach(System.out::println);
    }
}
/* output: java.util.stream.ReferencePipeline$Head java.util.stream.ReferencePipeline$Head java.util.stream.ReferencePipeline$Head */

We hope to get the character stream , But what we actually get is “Head” Flow of flow . We can use flatMap() To solve

		Stream.of(1, 2, 3)
                .flatMap(i -> Stream.of("Gonzo", "Kermit", "Breaker"))
                .forEach(System.out::println);
        /* output: Gonzo Kermit Breaker Gonzo Kermit Breaker Gonzo Kermit Breaker */

Here, each element stream returned from the mapping is transformed into elements .
Here's another demonstration , Start with an integer stream , Then use each integer element to create more random numbers .

		Stream.of(1, 2, 3, 4, 5)
                .flatMapToInt(i -> IntStream.concat(
                        rand.ints(0, 100).limit(i), IntStream.of(-1)))
                .forEach(n -> System.out.format("%d ", n));
                /*output: 58 -1 55 93 -1 61 61 29 -1 68 0 22 7 -1 88 28 51 89 9 -1 */

Introduced here concat(), It combines two streams in parameter order .

Consider again the previous task of dividing the file into word streams . The last thing to use is FileToWordsRegexp.java, The problem with it is that you need to read the entire file into the line list — The list needs to be stored .
What we need is to create a word stream that does not need an intermediate storage layer :

public class FileToWords {
    
    public static Stream<String> stream(String filePath) throws IOException {
    
        return Files.lines(Paths.get(filePath))
                .skip(1)
                .flatMap(line -> Pattern.compile("\\W+").splitAsStream(line));
    }
}

stream() Now it's a static method , Because it can complete the whole flow creation process by itself .
Be careful :\\W+ Is a regular expression . Express “ Nonword character ”, In lowercase form w Express “ Word characters ”.
In the above code Pattern.compile().splitAsStream() The result is flow , That means when we just want a simple word stream , Call... On the incoming rowstream map() It will produce a stream of words . So the solution is : Use flatMap() Flatten the flow of element flow into a simple element flow , perhaps , We can use String.split() Generate an array , It can be Arrays.stream() Turn into a stream .
Because with real flow , Not a stream of streams , So every time you need a new stream , We all have to start from scratch , Because streams cannot be reused .

Because we may encounter when operating the flow “ Air flow “, The way to solve empty flow is to use Optional class . So if you are interested here, you can read this article Optional Class uses

5、 ... and 、 Terminal operation

The following operation will get the final result of the stream . At this point, we can't go back and forth . so to speak : Terminal operation is the last thing we can do in the flow pipeline .

5.1 Array

  • toArray(): Convert the stream to an array of the appropriate type .
  • toArray(generator): In special circumstances , Generate arrays of custom types .

When we need data of array type to facilitate subsequent operations , The above method is very useful .

public class RandInts {
    
    private static int[] rints = new Random(47)
            .ints(0, 1000)
            .limit(100)
            .toArray();// Convert the stream to an array 

    public static IntStream rands() {
    
        return Arrays.stream(rints);// Converting arrays to streams 
    }
}

The above example will 100 A number in the range of 0 - 1000 The random number stream between is converted into an array and stored in rints in . thus , Every time you call rands() You can get the same integer stream repeatedly when you use it .

5.2 loop

  • forEach(Consumer): Common as :System.out::println As Consumer function .
  • forEachOrdered(Consumer): Guarantee forEach Operate in the original stream order .

The first form : Unordered operation , It makes sense only when parallel flows are introduced , Here is a brief introduction parallel(): It can realize multi processor parallel operation , The implementation principle is to divide the flow into multiple ( Usually the number is CPU The core number ) And perform operations on different processors . Because we are taking an internal iteration , So this can be achieved .
parallel() It seems simple , It's really tricky . More will be sorted out in concurrent programming .
Let's introduce parallel() To help understand forEachOrdered(Consumer) The role and usage scenarios of .

public class ForEach {
    
    static final int SZ = 14;

    public static void main(String[] args) {
    
        rands().limit(SZ)
                .forEach(value -> System.out.format("%d ", value));
        System.out.println(
        );
        rands().limit(SZ)
                .parallel()
                .forEach(n -> System.out.format("%d ", n));
        System.out.println();
        rands().limit(SZ)
                .parallel()
                .forEachOrdered(n -> System.out.format("%d ", n));
    }
}
/* output: 258 555 693 861 961 429 868 200 522 207 288 128 551 589 551 589 861 288 555 868 693 207 128 200 961 429 258 522 258 555 693 861 961 429 868 200 522 207 288 128 551 589 */

In the first stream , not used parallel(), So take the element from rands() Output the results in sequence . In the second stream , introduce parallel(), Even if the flow is small , The output results are also different , This is the result of multiprocessor parallel operation , If you run it several times , You will find that the output is different .
In the last stream , Use at the same time parallel() and forEachOrdered(Consumer) To force the original stream data to be preserved . Therefore, the use of the latter method has no effect on non parallel flows .

5.3 aggregate

  • collect(Collector): Use Collector Collect stream elements into the result set .
  • collect(Supplier,BiConsumer,BiConsumer): ditto , The first parameter creates a new result set , The second parameter collects the next element into the result set , The third parameter is used to combine the two results .

Suppose that our current requirement is to ensure that the elements are in order , Store elements in TreeSet in . although Collectors There's no specific toTreeSet(), But we can do this by passing the constructor reference of the collection to Collectors.toCollection(), To build any type of collection .

public class TreeSetOfWords {
    
    public static void main(String[] args) throws IOException {
    
        Set<String> words2 = Files.lines(Paths.get("C:\\demo\\src\\main\\com\\thingInJava\\streamsProgram\\optional\\TreeSetOfWords.java"))
                .flatMap(s-> Arrays.stream(s.split("\\W+")))//split Will be based on the parameters String Cut to array ,
                /*.flatMap(new Function<String, Stream<String>>() { @Override public Stream<String> apply(String s) { System.out.println(s); return Arrays.stream(s.split("\\W+")); } })*/
                .filter(s->!s.matches("\\d+"))
                .map(String::trim)// Remove... From the string “ ”
                .filter(s->s.length()>2)
                .limit(100)
                .collect(Collectors.toCollection(TreeSet::new));
        System.out.println(words2);
    }
}
/* output: [Arrays, Collectors, Downloads, Files, IOException, Paths, Set, String, System, TreeSet, TreeSetOfWords, Users, args, class, collect, com, demo, file, filter, flatMap, get, import, java, length, limit, lines, main, map, matches, new, nio, optional, out, package, println, public, split, src, static, stream, streamsProgram, thingInJava, throws, toCollection, trim, util, void, words2] */

The whole execution process is :Files.lines() open Path And convert it into a stream of rows . The next line of code takes a line from the stream collection (String) Segmentation ( Array ) And rewritten into a stream , Last flatMap Multiple word streams formed from each line , Flat mapping into a word stream . The next line removes all numeric strings , then trim Remove the space around the word ,filter Method to filter all items with a length less than 3 's words , And take the front of the final result 100 Save them in TreeSet in .

We can also generate in the stream Map.

class Pair{
    
    public final Character c;
    public final Integer i;

    public Pair(Character c, Integer i) {
    
        this.c = c;
        this.i = i;
    }

    @Override
    public String toString() {
    
        return "Pair{" +
                "c=" + c +
                ", i=" + i +
                '}';
    }

    public Character getC() {
    
        return c;
    }

    public Integer getI() {
    
        return i;
    }
}

class RandomPair{
    
    Random rand = new Random(47);
    // Infinite iterators for random upper case letters , call iterator Method will return the iterator of the stream element ; Here is the iterator of these upper case child parent streams 
    Iterator<Character> captures = rand.ints(65, 91)
            .mapToObj(i -> (char) i)
            .iterator();

    public Stream<Pair> stream() {
    
        return rand.ints(100, 1000)
                .distinct()
                // Because the above is random int Type of flow and de duplication , therefore , Corresponding mapToObject The internal parameter of is IntFunction<Object>(),
                //  Its default method is public Object apply(int value) {return null;}; Here we will Object Change to what we need Pair type 
                .mapToObj(i -> new Pair(captures.next(), i));
    }
}
public class MapCollector {
    
    public static void main(String[] args) {
    
        Map<Integer, Character> map = new RandomPair().stream()
                .limit(8)
                .collect(Collectors.toMap(Pair::getI, Pair::getC));
        System.out.println(map);
    }
}
/* output: {688=W, 309=C, 293=B, 761=N, 858=N, 668=G, 622=F, 751=N} */

RandomPair Created randomly generated Pair Object flow , stay Java in , We can't just combine two streams in some way . So here we create a stream of integers , And use mapToObj() Convert the integer stream to Pair flow .capChars The random uppercase iterator of creates a stream , then next() So that we can stream() This stream is used in . As far as the author knows , This is the only way to combine multiple streams into a new object stream .

ad locum , We only use the simplest form of Collectors.toMap(), This method only needs two functions to get the key and value from the stream . There are other forms of overloading , One is when a key conflict occurs , Use a function to handle conflicts .
Most of the time ,java.util.stream.Collectors Preset Collector Can meet our requirements . besides , You can also use the second form of collect().

public class SpecialCollector {
    
    public static void main(String[] args) throws IOException {
    
        ArrayList<String> words = FileToWords.stream("C:\\Users\\gt136\\Downloads\\Documents\\cheese.txt")
                .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
        words.stream()
                .filter(s->s.equals("cheese"))
                .forEach(System.out::println);
    }
}
/* output: cheese cheese */

5.4 Combine

  • reduce(BinaryOperator): Use BinaryOperator To combine elements in all streams . Because the stream may be empty , Its return value is Optional.
  • reduce(identity,BinaryOperator): The function is the same as above , But use identity As the initial value of its combination . Because if the flow is empty ,identity It's the result .
  • reduce(identity,BFunction,BinaryOperator): More complex forms of use , It's included here , Because it can improve efficiency . Usually , We can explicitly combine map() and reduce() To express it more simply .
class Frobnitz {
    
    int size;
    Frobnitz(int size){
    
        this.size = size;
    }

    @Override
    public String toString() {
    
        return "Frobnitz{" +
                "size=" + size +
                '}';
    }

    static Random rand = new Random(47);
    static final int BOUND = 100;
    // Random generation 100 Inside 
    static Frobnitz supply() {
    
        return new Frobnitz(rand.nextInt(BOUND));
    }
}
public class Reduce {
    
    public static void main(String[] args) {
    
        Stream.generate(Frobnitz::supply)
                .limit(10)
                .peek(System.out::println)
                //reduce The function of the method is to traverse the elements in the stream once , For the first time, the first parameter is null, The second parameter is the first stream element , The next first parameter is the element after the last operation , The second parameter is the next stream element 
                .reduce((fr0, fr1) -> fr0.size < 50 ? fr0 : fr1)
                .ifPresent(System.out::println);// To 29 The above method was not satisfied before , So values are constantly being replaced , After that, it will always be less than 50 Of 29
    }
}
/* output: Frobnitz{size=58} Frobnitz{size=55} Frobnitz{size=93} Frobnitz{size=61} Frobnitz{size=61} Frobnitz{size=29} Frobnitz{size=68} Frobnitz{size=0} Frobnitz{size=22} Frobnitz{size=7} Frobnitz{size=29} */

because supply() Method as a Supplier It's signature compatible , We can supply Method is passed to as a method reference Stream.generate()( This signature is called structural consistency ). Have we used “ Initial value ” As the first parameter reduce() Method , So the result is Optional type .Optional.ifPresent() Method is called only if the result is not empty Consumer<>(println Methods can be called because Frobnitz Can pass toString Method to String).
Lambda The first argument to the expression fr0 yes reduce() Is the result of the last call ,fr1 Is the value passed from the stream .
reduce() Medium Lambda The expression uses a ternary expression , When fr0 Of size Less than 50 when , take fr0 As a result , Otherwise... In the sequence fr1 As a result . When you get the first size Less than 50 Of Frobnitz, As long as this result is obtained, other elements in the stream will be ignored , This is a very strange limitation .

5.5 lookup

  • findFirst(): Returns the... Of the first stream element Optional, Return if the stream is empty Optional.empty.
  • findAny(): Returns the... Containing any stream element Optional, Return if the stream is empty Optional.empty.
import static com.gui.demo.thingInJava.streamsProgram.optional.RandInts.*;
public class SelectElement {
    
    public static void main(String[] args) {
    
        //rands Method to convert an array into a stream ,findFirst
        System.out.println(rands().findFirst().getAsInt());
        System.out.println(rands().parallel().findFirst().getAsInt());
        System.out.println(rands().findAny().getAsInt());
        System.out.println(rands().parallel().findAny().getAsInt());
    }
}
/* output: 258 258 258 242 */

Whether the stream is parallelized or not ,findFirst() Always select the first element in the stream . For nonparallel streams ,findAny() The first element in the stream is selected ( Even if, by definition, any element is selected ). In this case , use parallel() Parallelize streams , To show findAny() The possibility of not selecting the first element of the stream .

If it is necessary to select the last element in the stream , Use it reduce(). The code is as follows :

public class LastElement {
    
    public static void main(String[] args) {
    
        OptionalInt last = IntStream.range(10, 20)
                .reduce((n1, n2) -> n2);
        System.out.println(last.orElse(-1));

        Optional<String> lastobj = Stream.of("one", "two", "three")
                .reduce((n1, n2) -> n2);
        System.out.println(lastobj.orElse("Nothing there"));
    }
}
/* output: 19 three */

reduce() The argument to is just to replace the last two elements with the last one , In the end, only the last element is generated . If it is a digital stream , You have to use similar numbers Optional type , Otherwise use Optional type , Like in the example above Optional<String>.

5.6 Information

  • count(): The number of elements in the stream .
  • max(Comparator): Based on the incoming Comparator Determined by “ Maximum ” Elements .
  • min(Comparator): Based on the incoming Comparator Determined by “ Minimum ” Elements .

String The type has a preset Comparator Realization .

public class Informational {
    
    public static void main(String[] args) throws IOException {
    
        System.out.println(FileToWords.stream("C:\\Users\\gt136\\Downloads\\Documents\\cheese.txt").count());
        System.out.println(FileToWords.stream("C:\\Users\\gt136\\Downloads\\Documents\\cheese.txt")
                .min(String.CASE_INSENSITIVE_ORDER)
                .orElse("NONE"));
        System.out.println(FileToWords.stream("C:\\Users\\gt136\\Downloads\\Documents\\cheese.txt")
                .max(String.CASE_INSENSITIVE_ORDER)
                .orElse("NONE"));
    }
}
/* output: 32 a you */

min Methods and max The return type of the method is Optional, This requires us to use orElse() To unpack .

5.7 Digital stream information

  • average(): Take the average of the stream elements .
  • max() and min(): Numerical flow operations do not require Comparator.
  • sum(): Sum all flow elements .
  • summaryStatistics(): Generate potentially useful data .( It's not what it takes. )
public class NumericStreamInfo {
    
    public static void main(String[] args) {
    
        System.out.println(rands().average().getAsDouble());
        System.out.println(rands().max().getAsInt());
        System.out.println(rands().min().getAsInt());
        System.out.println(rands().sum());
        System.out.println(rands().summaryStatistics());
    }
}
/* output: 507.94 998 8 50794 IntSummaryStatistics{count=100, sum=50794, min=8, average=507.940000, max=998} */

5.8 matching

  • allMatch(predicate): If each element in the stream is provided to Predicate All back to true, The result returned to true. At the first false when , The calculation is stopped .
  • anyMatch(predicate): If any element of the stream is provided to Predicate return true, The result returned to true. At the first true Stop counting .
  • noneMatch(predicate): If each element of the stream is provided to Predicate All back to false when , The result returned to true. At the first true Stop execution of the calculation .

We have seen it before noneMatch() Use , The other two are similar . To eliminate redundancy , We created show(). First of all, we must know how to uniformly describe the operations of the three matchers , And then turn it into Matcher Interface .

interface Matcher extends BiPredicate<Stream<Integer>, Predicate<Integer>> {
    

}
public class Matching {
    
    static void show(Matcher match, int val) {
    
        System.out.println(
                match.test(
                        IntStream.rangeClosed(1, 9)
                                .boxed()
                                .peek(n -> System.out.format("%d ", n)),
                        n -> n < val));
    }

    public static void main(String[] args) {
    
        show(Stream::allMatch, 10);
        show(Stream::allMatch, 4);
        show(Stream::anyMatch, 2);
        show(Stream::anyMatch, 0);
        show(Stream::noneMatch, 5);
        show(Stream::noneMatch, 0);
    }
}
/* output: 1 2 3 4 5 6 7 8 9 true 1 2 3 4 false 1 true 1 2 3 4 5 6 7 8 9 false 1 false 1 2 3 4 5 6 7 8 9 true */

BiPredicate It's a binary predicate , It takes two parameters and returns true perhaps false. The first parameter is the stream we want to test , The second parameter is a predicate Predicate.Matcher Can match all Stream::Match Method , So you can put every Stream::Match Method reference passed to show In the method , Yes match.test() The call to is converted to a method reference Stream::Match Call to .

show() Parameters of val In the judgment test n < val The maximum value... Is specified in .show Method produces an integer 1-9 A stream composed of .peek() It is used to check the test step before testing the short circuit . It can be seen from the output that a short circuit has occurred each time .

原网站

版权声明
本文为[tragically unhappy]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/172/202206210928213475.html