Follow the public account JavaStorm to learn more exciting

Java 8 introduced a new feature called Lambda expression streams. When streams are used in conjunction with Lambda expressions, code becomes much cleaner and cleaner.

Super move. Release the code

Suppose there is a need to process the invoice information queried in the database:

  1. Take out the invoice of less than 10000.
  2. Sort the filtered data.
  3. Gets the sorted invoice seller name.

The invoice Model

@Builder
@Data
public class Invoice implements Serializable {
    /**
     * 销方名称
     */
    private String saleName;
    /** Is invalid */
    private Boolean cancelFlag;
    /**
     * 开票金额
     */
    private BigDecimal amount;
    /** * Type of invoice */
    private Integer type;
    /** * Number of details */
    private Integer detailSize;
}

Copy the code

We implement it the traditional way, before we initialize the test data

public class StreamTest {

    private List<Invoice> invoiceList;

    @Before
    public void initData(a) {
        Invoice invoice = Invoice.builder().amount(BigDecimal.valueOf(100.02)).cancelFlag(false).detailSize(10)
                .saleName("Guangxi Pharmaceutical").type(1).build();
        Invoice invoice2 = Invoice.builder().amount(BigDecimal.valueOf(89032478.9)).cancelFlag(false).detailSize(2)
                .saleName("Shenzhen Electronic Technology").type(1).build();
        Invoice invoice3 = Invoice.builder().amount(BigDecimal.valueOf(2077777889)).cancelFlag(true).detailSize(6)
                .saleName("The universe is empty.").type(1).build();
        Invoice invoice4 = Invoice.builder().amount(BigDecimal.valueOf(356.8)).cancelFlag(false).detailSize(10)
                .saleName("Mengda restaurant").type(2).build();
        Invoice invoice5 = Invoice.builder().amount(BigDecimal.valueOf(998.88)).cancelFlag(false).detailSize(0)
                .saleName("Internet celebrity restaurant").type(2).build();
        Invoice invoice6 = Invoice.builder().amount(BigDecimal.valueOf(9009884.09)).cancelFlag(false).detailSize(1)
                .saleName("Motor vehicle").type(3).build();
        invoiceList = Stream.of(invoice, invoice2, invoice3, invoice4, invoice5, invoice6).collect(Collectors.toList());
        System.out.println("Raw data:" + invoiceList.toString());
    }
Copy the code

Pre-java 8 implementation

/** * Select invoices with the amount less than 10000, sort according to the amount, and get the sorted list of seller names */
    @Test
    public void testJava7(a) {
        ArrayList<Invoice> lowInvoiceList = new ArrayList<>();
        // Select the invoice with the amount less than 10000
        for (Invoice invoice: invoiceList) {
            if (invoice.getAmount().compareTo(BigDecimal.valueOf(10000))"0) { lowInvoiceList.add(invoice); }}// Order the selected invoices
        lowInvoiceList.sort(new Comparator<Invoice>() {
            @Override
            public int compare(Invoice o1, Invoice o2) {
                returno1.getAmount().compareTo(o2.getAmount()); }});// Get the sorted name of the seller
        ArrayList<String> nameList = new ArrayList<>();
        for(Invoice invoice : lowInvoiceList) { nameList.add(invoice.getSaleName()); }}Copy the code

Java8 after the SAO gas operation, at one go. No more overtime writing long, stinking code

@Test
public void testJava8(a) {
  List<String> nameList = invoiceList.stream()
    .filter(item -> item.getAmount().compareTo(BigDecimal.valueOf(10000))"0)// Filter the data
    .sorted(Comparator.comparing(Invoice::getAmount))// Sort the amount in ascending order
    .map(Invoice::getSaleName)// Extract the name
    .collect(Collectors.toList());// Convert to list

}
Copy the code

A set of dragon service feeling, send you to the sky at one go. Greatly reduces the amount of code.

Now there’s another demand

Return a Map<Integer, List> of the invoice data.

Review the Java7 writing method, there is no one I wipe, this is too troublesome. And get off work early and hug your girlfriend.

@Test
public void testGroupByTypeJava7(a) {
  HashMap<Integer, List<Invoice>> groupMap = new HashMap<>();
  for (Invoice invoice : invoiceList) {
    // Append if it exists
    if (groupMap.containsKey(invoice.getType())) {
      groupMap.get(invoice.getType()).add(invoice);
    } else {
      // If it does not exist, initialize the addition
      ArrayList<Invoice> invoices = new ArrayList<>();
      invoices.add(invoice);
      groupMap.put(invoice.getType(), invoices);
    }
  }
  System.out.println(groupMap.toString());
}
Copy the code

Next, we use stream’s SAO operation code to achieve the above requirements

GroupingBy grouping

@Test
public void testGroupByTypeJava8(a) {
  Map<Integer, List<Invoice>> groupByTypeMap = invoiceList.stream().collect(Collectors.groupingBy(Invoice::getType));
}
Copy the code

It’s that simple, one line of code.

What is Stream?

A Stream is a queue of elements from a data source and supports aggregation operations. It is not a data structure and does not hold data, primarily for computing purposes.

Elements are objects of a specific type that form a queue. Streams in Java do not store elements, but are computed on demand. Data Source Indicates the source of a stream. It can be collections, arrays, I/O channels, generator generators, etc. Aggregation operations Operations similar to SQL statements, such as Filter, map, Reduce, find, match, and sorted. Unlike the previous Collection operation, the Stream operation has two basic features:

  • Pipelining: Intermediate operations will return the stream object itself. This allows multiple operations to be cascaded into a single pipeline, similar to the Fluent style. This allows for optimization of operations, such as delayed execution and short-circuiting.
  • Internal iteration: Previously, iterating over a set was done by either Iterator or for-each, explicitly iterating outside the set. This is called external iteration. Stream provides a way to iterate internally, through the Visitor pattern.

How to generate streams

There are five main ways

1. Generate from a set

Collection<String> collection = Arrays.asList("a"."b"."c");
Stream<String> streamOfCollection = collection.stream();
Copy the code

2. Generate an array

int[] intArr = new int[] {1.2.3.4.5};
IntStream stream = Arrays.stream(intArr);
Copy the code

Streams are generated through the Arrays.stream method, and the generated stream is an IntStream rather than stream

. An added bonus is that using numerical streams can improve performance by avoiding unpacking during calculations.

The Stream API provides mapToInt, mapToDouble, and mapToLong methods to convert a Stream of objects to a Stream of numbers, and the boXED method to convert a Stream of numbers to a Stream of objects

3. Generate by value

Stream<Integer> stream = Stream.of(1.2.3.4.5);
Copy the code

Stream is generated by the of method of Stream, and an empty Stream is generated by the empty method of Stream

4. Generate a file

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

A stream is obtained through the files.line method, and each resulting stream is a line from a given file

5. Through function generation,Iterate and generate are two static methods that generate a stream from a function

Iterator: The iterator: method takes two arguments, the first is the initialization value, and the second is the function operation to perform. Because the stream generated by iterator is an infinite stream, it is truncated by the limit method, generating only five even numbers

Stream<Integer> stream = Stream.iterate(0, n -> n + 2).limit(5);
Copy the code

Generator: Takes a parameter, method parameter type Supplier, that provides the value to the stream. The stream generated by generate is also an infinite stream and is thus truncated by the LIMIT convection

Stream<Double> stream = Stream.generate(Math::random).limit(5);
Copy the code

The operation type of the stream

There are two main types

1. Intermediate operations

A stream can be followed by zero or more intermediate operations. The main goal is to open streams, do some sort of data mapping/filtering, and then return a new stream for the next operation to use.

This kind of operation is lazy. It only calls this kind of method and does not really start the flow traversal. The real traversal needs to wait until the terminal operation

2. Terminal operations

A stream can have only one terminal operation. When this operation is performed, the stream is closed and cannot be operated on again. Therefore, a stream can only be traversed once. The execution of the terminal operation will actually start the flow traversal. For example, count, Collect and so on will be introduced below.

Intermediate operation API

The filter screen

Stream<Invoice> invoiceStream = invoiceList.stream().filter(invoice -> invoice.getDetailSize() < 10);
Copy the code

Distinct Removes duplicate elements

List<Integer> integerList = Arrays.asList(1.1.2.3.4.5);
Stream<Integer> stream = integerList.stream().distinct();
Copy the code

Limit Returns the number of streams

Stream<Invoice> invoiceStream = invoiceList.stream().limit(3);
Copy the code

Use the limit method to specify the number of streams to return. The limit parameter must be >=0, otherwise an exception will be thrown

Skip skips the elements in the stream

 List<Integer> integerList = Arrays.asList(1.1.2.3.4.5);
 Stream<Integer> stream = integerList.stream().skip(2);
Copy the code

Skip the elements in the stream with the skip method. The above example skips the first two elements, so the print result is 2,3,4,5. The value of skip must be >=0 or an exception will be thrown.

The map flow mapping

Flow mapping is the mapping of an accepted element to another element

List<String> stringList = Arrays.asList("Java 8"."Lambdas"."In"."Action");
Stream<Integer> stream = stringList.stream().map(String::length);
Copy the code

This example completes the mapping of String -> Integer. The previous example completes the mapping of Invoice -> String through the map method

FlatMap flow conversion

Converts each value in one stream to another

List<String> wordList = Arrays.asList("Hello"."World");
        List<String> strList = wordList.stream()
                .map(w -> w.split(""))// Stream
      []>
                .flatMap(Arrays::stream)// Convert Stream
      
        to Stream
       []>
                .distinct() / / to heavy
                .collect(Collectors.toList());
        System.out.println(strList.toString());
Copy the code

Map (w -> w.split(” “)) returns Stream

. So the final print is [H, E, L, O, W, r, d]
[]>

Match elements

  1. AllMatch Matches all
if (invoiceList.stream().allMatch(Invoice::getCancelFlag)) {
  System.out.println("All the invoices are invalid.");
}
Copy the code
  1. AnyMatch Matches one of them

Invalid invoices are printed

if (invoiceList.stream().anyMatch(Invoice::getCancelFlag)) {
  System.out.println("Invalid invoice exists");
}
Copy the code

Is equivalent to

for (Invoice invoice : invoiceList) {
  if (invoice.getCancelFlag()) {
    System.out.println("Invalid invoice exists");
    break; }}Copy the code
  1. NoneMatch all mismatches
List<Integer> integerList = Arrays.asList(1.2.3.4.5);
if (integerList.stream().noneMatch(i -> i > 3)) {
    System.out.println(All values are less than 3.);
}
Copy the code

Terminal operation

Count the number of elements in the stream

  1. Use the count
long count = invoiceList.stream()
  .filter(item -> item.getAmount().compareTo(BigDecimal.valueOf(10000))"0)
  .count();
Copy the code
  1. Using the counting
long count = invoiceList.stream()
  .filter(item -> item.getAmount().compareTo(BigDecimal.valueOf(10000))"0)
  .collect(Collectors.counting());
Copy the code

This last method of counting the number of elements is especially useful when used in conjunction with collect

To find the

  1. FindFirst looks for the first one
Optional<Invoice> first = invoiceList.stream()
  .filter(item -> item.getAmount().compareTo(BigDecimal.valueOf(10000))"0)
  .findFirst();
Copy the code

Find the first element with an amount less than 10,000 using findFirst

  1. FindAny Random search
Optional<Invoice> any = invoiceList.stream()
  .filter(item -> item.getAmount().compareTo(BigDecimal.valueOf(10000))"0)
  .findAny();
Copy the code

The findAny method finds one of the elements less than 10000 and prints it. Because of internal optimization, it ends when it finds the first element that satisfies a value greater than three. The method results are the same as the findFirst method. The findAny method is provided to make better use of parallel flows; the findFirst method is more limited on parallelism [this article will not cover parallel flows]

Reduce combines elements in a flow

Suppose we sum the values in a set

Jdk8 before

int sum = 0;
for (int i : integerList) {
	sum += i;
}
Copy the code

After jdK8 is processed, reduce is used

int sum = integerList.stream().reduce(0, (a, b) -> (a + b));
// You can also write using method references
int sum = integerList.stream().reduce(0, Integer::sum);
Copy the code

Such as the sum of the invoice amount

BigDecimal reduce = invoiceList.stream().map(Invoice::getAmount).reduce(BigDecimal.ZERO, (a, b) -> (a.add(b)));
Copy the code

Continue to simplify using method references

BigDecimal reduce = invoiceList.stream().map(Invoice::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
Copy the code

Reduce takes two arguments, an initial value 0 in this case, and a BinaryOperator

Accumulator to combine the two elements to produce a new value,

In addition, the reduce method has an overloaded method with no initialization value

Gets the minimum maximum value in the stream

The min/ Max command is used to obtain the minimum maximum value

Optional<BigDecimal> min = invoiceList.stream().map(Invoice::getAmount).min(BigDecimal::compareTo);
Optional<BigDecimal> max = invoiceList.stream().map(Invoice::getAmount).max(BigDecimal::compareTo);
Copy the code

Or you could write it as

OptionalInt min1 = invoiceList.stream().mapToInt(Invoice::getDetailSize).min();
OptionalInt max1 = invoiceList.stream().mapToInt(Invoice::getDetailSize).max();
Copy the code

Min gets the minimum value in the stream and Max gets the maximum value in the stream. The method parameter is Comparator
comparator

Get the minimum maximum value by minBy/maxBy

invoiceList.stream().map(Invoice::getAmount).collect(Collectors.minBy(BigDecimal::compareTo)).get();
Copy the code

Obtain the minimum and maximum values through Reduce

Optional<BigDecimal> max = invoiceList.stream().map(Invoice::getAmount).reduce(BigDecimal::max);
Copy the code

sum

Through summingInt

Integer sum = invoiceList.stream().collect(Collectors.summingInt(Invoice::getDetailSize));
Copy the code

If the data type is double, long, the summingDouble, summingLong methods are used to sum

Through the reduce

Integer sum = invoiceList.stream().map(Invoice::getDetailSize).reduce(0, Integer::sum);
Copy the code

By sum, the best way to write it

// Recommend writing
Integer sum = invoiceList.stream().mapToInt(Invoice::getDetailSize).sum();
Copy the code

When summing, maximizing, and minimizing the above, there are different ways to perform the same operation. Collect, reduce, min/ Max, and sum can be selected. Min, Max, and sum are recommended. Because it is the most concise and easy to read, it also uses mapToInt to convert the stream of objects into a stream of numbers, avoiding boxing and unboxing

Average by averagingInt

Double avg = invoiceList.stream().collect(Collectors.averagingInt(Invoice::getDetailSize));
Copy the code

If the data type is double or long, the average is obtained using the average double and average Long methods

For BigDecimal you sum and then divide by the total number of entries

List<BigDecimal> sumList = invoiceList.stream().map(Invoice::getAmount).collect(Collectors.toList());
        BigDecimal average = average(sumList, RoundingMode.HALF_UP);
// Take the average value
public BigDecimal average(List<BigDecimal> bigDecimals, RoundingMode roundingMode) {
  BigDecimal sum = bigDecimals.stream()
    .map(Objects::requireNonNull)
    .reduce(BigDecimal.ZERO, BigDecimal::add);
  return sum.divide(new BigDecimal(bigDecimals.size()), roundingMode);
}
Copy the code

Sum, average, maximum, and minimum all at once using summarizingInt

IntSummaryStatistics statistics = invoiceList.stream().collect(Collectors.summarizingInt(Invoice::getDetailSize));
double average1 = statistics.getAverage();
int max1 = statistics.getMax();
int min1 = statistics.getMin();
long sum = statistics.getSum();
Copy the code

Element traversal is done through foreach

invoiceList.forEach(item -> {
  System.out.println(item.getAmount());
});
Copy the code

Joining elements in a stream by joining

String result = invoiceList.stream().map(Invoice::getSaleName).collect(Collectors.joining(","));
Copy the code

Group by groupingBy

 Map<Integer, List<Invoice>> groupByTypeMap = invoiceList.stream().collect(Collectors.groupingBy(Invoice::getType));
Copy the code

Pass groupingBy into the collect method for grouping, where the method parameter of groupingBy is the classification function. You can also use groupingBy for multi-level classification through nesting

Map<String, Map<String, List<RzInvoice>>> = invoiceList.stream().collect(Collectors.groupingBy(Invoice::getType, Collectors.groupingBy(invoice -> {
    if (invoice.getAmount().compareTo(BigDecimal.valueOf(10000)) < =0) {
        return "low";
    } else if (invoice.getAmount().compareTo(BigDecimal.valueOf(80000)) < =0) {
        return "mi";
    } else {
        return "high"; }})));Copy the code

Map<String, Map<String, List>>

Advancements are partitioned by partitioningBy

Special grouping, which is classified by true and false, so that the results returned can be divided into at most two groups

Map<Boolean, List<Dish>> = invoiceList.stream().collect(Collectors.partitioningBy(RzInvoice::getCancelFlag));
Copy the code

Is equivalent to

Map<Boolean, List<Dish>> = invoiceList.stream().collect(Collectors.groupingBy(RzInvoice::getCancelFlag));
Copy the code

This example may not tell the difference between partitioning and classification, and may even make partitions unnecessary. Let’s try a more obvious example:

List<Integer> integerList = Arrays.asList(1.2.3.4.5);
Map<Boolean, List<Integer>> result = integerList.stream().collect(partitioningBy(i -> i < 3));
Copy the code

The key of the return value is still Boolean, but its classification is by range, and partitioning is better suited to handle sorting by range

Here’s an example from my work

// Filter the data from T-1 to T-12 in the last 12 months, sum the invoice amount according to the provinces, and use the amount in reverse order to generate a LinkedHashMap
        LinkedHashMap<String, BigDecimal> areaSortByAmountMaps =
                invoiceStatisticsList.stream().filter(FilterSaleInvoiceUtil.filterSaleInvoiceWithRange(1.12, analysisDate)) // Filter data by time
                        .collect(Collectors.groupingBy(FkSalesInvoiceStatisticsDO::getBuyerAdministrativeAreaCode
                                , Collectors.reducing(BigDecimal.ZERO, FkSalesInvoiceStatisticsDO::getInvoiceAmount, BigDecimal::add)))// Group according to the billing area and sum the billing amount of each grouping data simultaneously
                        .entrySet().stream().sorted(Map.Entry.<String, BigDecimal>comparingByValue().reversed()) // In reverse order according to the amount
                        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)); // Collect data to generate a LinkedHashMap
Copy the code

conclusion

By using the Stream API, you can simplify your code while improving the readability of your code and get it ready for use in your project. Before I learned the Stream API, I wanted to kick anyone who wrote a lot of lambdas and Stream apis in my application.

I think I might be in love with him now. Be careful not to mix declarative and imperative programming when using them together.