JDK1.8 series articles

  • New in JDK1.8 (1) : Lambda expressions
  • New in JDK1.8 (2) : Optional
  • JDK1.8 new feature (3) : Stream
  • New in JDK1.8 (4) : Maps
  • New JDK1.8 feature (5) : new date and time API

Stream is the key abstraction for dealing with collections in Java8. It specifies what you want to do with collections, and can perform very complex operations such as finding, filtering, and mapping data. Manipulating the collection data using the Stream API is similar to performing a database query using SQL. You can also use the Stream API to perform operations in parallel. The Stream API provides an efficient and easy-to-use way to process data.

What is Stream

By transforming the collection into a sequence of elements called a Stream, it is declaratively possible to pipeline each element of the collection in a series of parallel or serial operations. In other words, you just tell the stream what you want, and the stream will do what it wants behind the scenes, and you just sit back and enjoy it.

Second, flow operation

The entire flow operation is a pipeline where elements are processed one by one. The data source is the original collection, and then the collection such as List is converted into a Stream of type, and a series of intermediate operations are performed on the Stream, such as filtering and retaining some elements, sorting elements, type conversion, etc. Finally, there is a terminal operation, which converts the Stream back to the collection type, or directly processes the elements of the Stream, such as printing, calculating the total, maximizing, etc. Importantly, many Stream operations return a Stream themselves, so multiple operations can be directly connected. Let’s look at the code for a Stream operation:

List<String> list = new ArrayList<>();
// Add 20 string elements to list
list.add("Lucas");
List<Integer> streamList = list.stream().map(String::length).sorted().limit(10).collect(Collectors.toList());
// stream() converts the collection to a stream
// map(String::length) converts List
      
        to List
       
/ / sorted ()
// limit(10) keeps the first 10 elements
// collect(aggregators.tolist ()) converts the stream back to the collection
Copy the code

In the past, to do a series of operations like this, you would have to do an iterator or foreach loop and iterate through it, doing it yourself step by step. But if you use streams, you can use them declaratively directly, and streams do it for you.

Third, the difference between flow and set

1. When to calculate

A set that contains all the values in the current data structure, you can add or delete them at any time, but the elements in the set are undoubtedly already computed. The flow is calculated on demand, and the data is calculated according to the user’s needs. You can imagine that when we search through the search engine, not all the items found are displayed, and the top 10 or 20 items are displayed first. Only when the “next page” is clicked, the new 10 items will be output. The same goes for watching movies online and watching movies on your hard drive.

2. Iterative

The iteration mode of Stream is internal iteration, and the iteration mode of collection is external iteration. We can compare the collection to the warehouse of a factory. At the beginning, the factory is relatively backward. If the goods need to be modified, the workers can only go into the warehouse to deal with the goods, and sometimes they have to put the processed goods into a new warehouse. In this period, we need to personally do the iteration, one by one, to find the goods we need to deal with, this is called external iteration. Later the factory developed, equipped with assembly line operation, as long as the corresponding assembly line is designed according to the needs, and then the workers as long as the goods are put on the assembly line, they can wait to receive the results, and the assembly line can also directly transport the goods to the corresponding warehouse according to the requirements. This is called internal iteration, the pipeline has done the iteration for you, you just have to say what to do. Streams, like iterators, can only be iterated once. The third line in the following code will report an error because the second line has already used the stream and the stream has already been consumed.

List<String> list = new ArrayList<>();
Stream<Integer> stream =list.stream().map(String::length).sorted().limit(10);
List<Integer> newList = stream.collect(Collectors.toList());
List<Integer> newList2 = stream.collect(Collectors.toList());   / / an error
Copy the code

Java 8 introduced Streams in large part because internal iterations of streams automatically select a data representation and parallel implementation that is appropriate for your hardware; In the past, when programmers did things like foreach on their own, they had to manage parallelism and so on.

Four, common methods

First, let’s create a Person test class with two variables, age and name

public class Person {
    private String name;
    private int age;

    public Person(a) {}public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName(a) {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge(a) {
        return age;
    }

    public void setAge(int age) {
        this.age = age; }}Copy the code

Create a List of generic type Person

List<Person> list = new ArrayList<>();
list.add(new Person("jack".20));
list.add(new Person("mike".25));
list.add(new Person("tom".30));
Copy the code

1, stream()/parallelStream()

The most commonly used method to transform a collection into a stream

Stream stream = list.stream();
Copy the code

ParallelStream (), on the other hand, is a parallelStream method that allows the data set to perform parallel operations, as explained in more detail later

2, filter(T -> Boolean)

Keep the elements whose Boolean is true

// Keep the person element with age 20
list = list.stream()
            .filter(person -> person.getAge() == 20)
            .collect(Collectors.toList());
Copy the code

Print [Person{name=’jack’, age=20}], collect(toList()) to convert the stream to a List type, which will be explained later

3, distinct ()

To remove duplicate elements, this method determines whether two elements are equal using the equals method of the class. For example, the Person class defines equals first. Otherwise cases like [Person{name=’jack’, age=20}, Person{name=’jack’, age=20}] are not handled

4, sorted()/sorted((T, T) -> int)

If the elements in the stream have classes that implement the Comparable interface, that is, have their own sorting rules, then you can call the sorted() method to sort the elements directly, Instead of Stream, you need to call sorted((T, T) -> int) to implement the Comparator interface

// Compare by age:
list = list.stream()
        .sorted((p1, p2) -> p1.getAge() - p2.getAge())
        .collect(Collectors.toList());
Copy the code

And of course this can be simplified to

list = list.stream()
        .sorted(Comparator.comparingInt(Person::getAge))
        .collect(Collectors.toList());
Copy the code

5, limit (n)

Returns the first n elements

list = list.stream()
            .limit(2)
            .collect(Collectors.toList());

// Print [Person{name='jack', age=20}, Person{name='mike', age=25}]
Copy the code

6, the skip (n)

I’m going to remove the first n elements

list = list.stream()
            .skip(2)
            .collect(Collectors.toList());

// Print [Person{name=' Tom ', age=30}]
Copy the code


tips:

  • When used before limit(n), it removes the first M elements and returns the first N elements of the remaining elements
  • When used before skip(m), limit(n) returns the first N elements and removes m of the remaining N elements
list = list.stream()
            .limit(2)
            .skip(1)
            .collect(Collectors.toList());

// Print [Person{name='mike', age=25}]
Copy the code

7, map(T -> R)

Map each element T in the stream to R (similar type conversion)

List<String> newlist = list.stream()
        .map(Person::getName)
        .collect(Collectors.toList());
        
// Print out [Jack, Mike, Tom]
Copy the code

The element in newList is the name variable for each Person object in the list

3, flatMap(T -> Stream)

Each element T in the flow is mapped to a flow, and each flow is connected to a flow

List<String> list = new ArrayList<>();
list.add("aaa bbb ccc");
list.add("ddd eee fff");
list.add("ggg hhh iii");

list = list.stream()
        .map(s -> s.split(""))
        .flatMap(Arrays::stream)
        .collect(Collectors.toList());
        
// Print out [aaa, BBB, CCC, DDD, eee, FFF, GGG, HHH, iii]
Copy the code

In the example above, our goal is to split each string element in the List by “” to make a new List. First, the map method splits each String element, but the Stream is of type Stream

, because the split method returns type String[]; We need to use flatMap to make each String[] element a stream using Arrays::stream, and then flatMap to connect each String into a stream and return the stream we want
[]>

9, anyMatch(T -> Boolean)

Is there an element in the stream that matches the given T -> Boolean condition

// Is there a person object whose age is equal to 20?
boolean b = list.stream().anyMatch(person -> person.getAge() == 20);
Copy the code

10, allMatch(T -> Boolean)

Whether all elements in the stream match the given T -> Boolean condition

11, noneMatch(T -> Boolean)

Whether there are no elements in the stream that match the given T -> Boolean condition

FindAny () and findFirst()

  • FindAny () : Find one of the elements (the first element is found when using stream(); When parallelStream() is used, one of the elements is found.
  • FindFirst () : Finds the first element

Note that both methods return an Optional object, which is a container class that represents the presence or absence of a value

Reduce ((T, T) -> T) and reduce(T, (T, T) -> T)

Used to combine elements of a flow, e.g. summation, quadrature, maximization, etc

// Calculate the total age:
int sum = list.stream().map(Person::getAge).reduce(0, (a, b) -> a + b);
// The same as:
int sum = list.stream().map(Person::getAge).reduce(0, Integer::sum);
Copy the code

Where, the first parameter 0 of Reduce represents the starting value of 0, lambda (a, b) -> a + b is the addition of two values to produce a new value, as follows:

// Calculate the total age product:
int sum = list.stream().map(Person::getAge).reduce(1, (a, b) -> a * b);
Copy the code

Of course you can

Optional<Integer> sum = list.stream().map(Person::getAge).reduce(Integer::sum);
Copy the code

No initial value is accepted, but because there is no initial value, the result may not exist. Therefore, the Optional type is returned

14 and the count ()

Returns the number of elements in the stream. The result is of type long

15, collect ()

Collect (toList()), collect(toSet()), and so on. The parameter is a collector interface

16, forEach ()

Iterate over the elements

// Print each element:
list.stream().forEach(System.out::println);
Copy the code

V. Data flow

Int sum = list.stream().map(Person::getAge).reduce(0, Integer::sum); The method of calculating the sum of elements implies the packing cost. The map(Person::getAge) method then flows into the Stream type, while each Integer has to be unboxed into a primitive type and summed by the sum method, which greatly affects the efficiency. Java 8 introduces IntStream, DoubleStream, and LongStream to address this problem. The elements in these streams are of the primitive data types int, double, and long

1, flow and numerical flow conversion

1.1 stream to numeric stream

  • mapToInt(T -> int) : return IntStream
  • mapToDouble(T -> double) : return DoubleStream
  • mapToLong(T -> long) : return LongStream
IntStream intStream = list.stream().mapToInt(Person::getAge);
Copy the code

Of course, this would be an error

LongStream longStream = list.stream().mapToInt(Person::getAge);
Copy the code

The getAge method returns an int (if it returns an Integer, it can also be converted to an IntStream).

1.2. Numerical stream to stream

Very simple, just one boxed

Stream<Integer> stream = intStream.boxed();
Copy the code

2. Numerical flow method

The names of these methods go without saying:

  • sum()
  • max()
  • min()
  • Business (such as)…

3. Range of values

IntStream and LongStream have range and rangeClosed methods for numeric range handling

  • IntStream: rangeClosed(int, int)/range(int, int)
  • LongStream: rangeClosed(long, long)/range(long, long)

The difference between these two methods is that one is a closed interval and the other is a half open and half closed interval:

  • RangeClosed (1, 100) : [1, 100]
  • Range (1, 100) : [1, 100]

We can generate a numeric stream from 1 to 100 using intstream.rangeclosed (1, 100)

// Sum the values from 1 to 10:
IntStream intStream = IntStream.rangeClosed(1.10);
int sum = intStream.sum();
Copy the code

Build flow

Before we got a stream from an original data source, we can actually build the stream directly.

1. Value creation stream

Stream.of(T…) : stream.of (“aa”, “bb”) generates a Stream

// Generate a string stream
Stream<String> stream = Stream.of("aaa"."bbb"."ccc");
Copy the code

Stream.empty() : generates an empty Stream

2. Array creation stream

Create streams based on the array types of the parameters:

  • Arrays.stream(T[ ])
  • Arrays.stream(int[ ])
  • Arrays.stream(double[ ])
  • Arrays.stream(long[ ])

    It’s also worth noting that you can specify that you only take parts of an array, usingArrays.stream(T[], int, int)
Take only the index1To the first2A:int[] a = {1.2.3.4};
Arrays.stream(a, 1.3);
Copy the code

3. File generation stream

Stream<String> stream = Files.lines(Paths.get("data.txt"));
Copy the code

Each element is one line of a given file

4. Function generation stream

Two ways:

  • Iterate: Apply the function to each newly generated value in turn
  • Generate: Takes a function and generates a new value
Stream.iterate(0, n -> n + 2);
// Generate a stream that starts with 0 and then adds 2

Stream.generate(Math :: random);
// Generate a stream of random doubles from 0 to 1

Stream.generate(() -> 1);
// Generate a stream with all elements 1
Copy the code

Collect data

The Coollect method, as a terminal operation, accepts a Collector interface parameter and can perform some collection and aggregation operations on the data

1, collect

The most common method is to collect all elements in a stream into a List, Set, or Collection

  • toList
  • toSet
  • toCollection
  • toMap
List newlist = list.stream().collect(Collectors.toList());
// If the Key of the Map is repeated, an error will be reported
Map<Integer, Person> map = list.stream().collect(Collectors.toMap(Person::getAge, p -> p));
Copy the code

2, the summary,

  • Count count()
  • Sum ()
  • Average ()
// Count the total
long size = list.stream().count();
// Calculate the sum
int sum = list.stream().mapToInt(Person::getAge).sum();
// Take the average
Double average = list.stream().collect(Collectors.averagingInt(Person::getAge));
Copy the code
  • The methods summarizingInt, summarizingLong, and summarizingDouble are special. For example, summarizingInt returns type IntSummaryStatistics
IntSummaryStatistics l = list.stream().collect(Collectors.summarizingInt(Person::getAge));
Copy the code

IntSummaryStatistics includes the calculated mean, total, sum, and maximum values, which can be obtained by using the following methods: getAverage(), getCount(), getMax(), getMin(), and getSum().

3, take the maximum value

The maxBy and minBy methods require a Comparator interface as an argument

Optional<Person> optional = list.stream().max(Comparator.comparing(Person::getAge));
Copy the code

4. Joining string

Another common method is to concatenate string elements within the stream. The underlying implementation is a StringBuilder specifically for concatenating strings

String s = list.stream().map(Person::getName).collect(Collectors.joining(","));
// Result: Jack, Mike, Tom
Copy the code

There is a special overloading method for joining:

String s = list.stream().map(Person::getName).collect(Collectors.joining(" and "."Today "." play games."));
// Result: Today Jack and Mike and Tom play games.
Copy the code

Today drops the header, play games. Puts the end, and concatenates the strings in the middle

5. GroupingBy Group

GroupingBy is used to group the data, eventually returning a Map type

Map<Integer, List<Person>> map = list.stream().collect(Collectors.groupingBy(Person::getAge));
Copy the code

In this example, we group each Person object by age. Each Person object of the same age is grouped together. In addition, we can see that the Person::getAge determines the Map key (type Integer). GroupingBy can take a second argument to implement multilevel grouping:

Map<Integer, Map< String , List<Person>>> map = list.stream().collect(Collectors.groupingBy(Person::getAge, Collectors.groupingBy(Person::getName)));
Copy the code

The returned Map key is of type Integer and the value is of type Map

, that is, groupBy(…) in the parameter. The type (2) returned collects data by group
,>

Map<Integer, Integer> map = list.stream().collect(Collectors.groupingBy(Person::getAge, Collectors.summingInt(Person::getAge)));
Copy the code

In this example, we group by age, then summingInt(Person::getAge)) calculates the sum of ages (Integer) for each group and returns a Map
,>

6. PartitioningBy

Partitioning differs from grouping in that partitions are divided in terms of true and false, so the lambda of the argument to partitioningBy is also T -> Boolean

// Partition by age 20 or less
Map<Boolean, List<Person>> map = list.stream()
                .collect(Collectors.partitioningBy(p -> p.getAge() <= 20)); Print out {false=[Person{name='mike', age=25}, Person{name='tom', age=30}].true=[Person{name='jack', age=20}}]Copy the code

Similarly partitioningBy can add a collector as a second parameter, performing multiple partitions like groupBy, and so on.

Eight, parallel

We can convert a list type to a stream type with list.stream(), and we can convert to a parallelStream with list.parallelstream (). So you can usually use parallelStream instead of stream. A parallelStream is a stream that divides the content into chunks and uses different threads to process each chunk separately. This is also a feature of streams, since, prior to Java 7, it was cumbersome to process data sets in parallel. You had to split the data yourself, assign threads yourself, and make sure synchronization avoided contention when necessary. Stream makes it relatively easy for programmers to process data sets in parallel, but it’s important to note that this isn’t always the case. Sometimes parallelization is even less efficient than sequential processing, and sometimes it can lead to data handling errors due to thread-safety issues, which I’ll discuss in the next article. Take the following example

 int i = Stream.iterate(1, a -> a + 1).limit(100).parallel().reduce(0, Integer::sum);
Copy the code

We calculate the sum of all the numbers from 1 to 100 with a single line of code like this, and we use PARALLEL for parallelism. But in fact, this kind of computation is very inefficient, even less efficient than not using parallelism! This is partly because of the packing problem, which was mentioned earlier, and partly because it is difficult for the iterate method to break up the numbers into separate blocks for parallel execution, so it is less efficient.

Decomposability of flow

This brings us to the issue of stream factorability. When using parallelism, we need to pay attention to whether the data structure behind the stream is easy to decompose. For example, ArrayList and LinkedList are well known, with the former clearly dominating decomposition. Let’s look at the factorability of some data sources

The data source decomposability
ArrayList excellent
LinkedList poor
IntStream.range excellent
Stream.iterate poor
HashSet good
TreeSet good

Nine, efficiency,

Finally, efficiency. Many of you may have heard about the underlying problem of Stream efficiency. In fact, for some simple operations, such as simple traversal, finding the maximum value, and so on, Stream performance can be significantly lower than traditional loop or iterator implementations. However, for complex operations, such as some complex object reduction, the performance of Stream is comparable to that of manual implementations and, in some cases, far more efficient than manual implementations using parallel streams. Good steel used on the blade, in the appropriate scenario, can play its greatest use. The appearance of functional interface is mainly to improve the efficiency of coding development and enhance the readability of code. At the same time, actual development doesn’t always require very high performance, so streams and lambdas make a lot of sense.

This article reprinted from: www.jianshu.com/p/e429c517e…

The public,

If you want to see my updated Java direction learning article in the first time, you can follow the public account ** [Lucas’s Coffee shop]. All learning articles public number first, please scan the code to pay attention to oh! Java8 new features learning videos please pay attention to the public number, private message [Java8] ** can be free unscripted access to learning videos.