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:
- Take out the invoice of less than 10000.
- Sort the filtered data.
- 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
- AllMatch Matches all
if (invoiceList.stream().allMatch(Invoice::getCancelFlag)) {
System.out.println("All the invoices are invalid.");
}
Copy the code
- 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
- 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
- Use the count
long count = invoiceList.stream()
.filter(item -> item.getAmount().compareTo(BigDecimal.valueOf(10000))"0)
.count();
Copy the code
- 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
- 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
- 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.