This is the ninth day of my participation in the August More text Challenge. For details, see: August More Text Challenge
Java 8 introduced
- Java 8 was released in 2014 and, years later, is still the most commonly used JDK version
- Java 8 adds a number of features to improve code readability and simplify code, such as Lambda, Stream manipulation, ParallelStream, Optional types, and new date-time types
- The combination of Lambda expressions with Stream operations has greatly improved the efficiency of our daily coding
Lambda expressions
-
Anonymous class inner classes do not have class names, but they still give method definitions
-
The purpose of Lambda expressions is to further simplify the syntax of anonymous classes
-
In implementation, Lambda expressions are not syntactic sugar for anonymous classes
-
Examples of Lambda and anonymous classes
public class ThreadCreator { public static Thread createThreadByAnonymous(a) { return new Thread(new Runnable() { @Override public void run(a) { System.out.println("Anonymous thread print"); }}); }public static Thread createThreadByLambda(a) { return new Thread(() -> { System.out.println("Lambda thread print"); }); }}Copy the code
-
How do Lambda expressions match type interfaces? => Functional interface
A FunctionalInterface is an interface that has a single abstract method, described using @functional interface, that can be implicitly translated into a Lambda expression
An example of using Lambda expressions to create a functional interface makes functions first-class citizens of a program, and thus passes them as arguments like normal data
The JDK provides a number of native functional interfaces, such as Supplier, in java.util.function
@FunctionalInterface public interface Supplier<T> { /** * Gets a result. * * @return a result */ T get(a); } Copy the code
Use Lamda or method references to get an instance of a functional interface
// Provide the Supplier interface implementation with a Lambda expression that returns the OK string Supplier<String> stringSupplier = () ->"OK"; // Supply the Supplier interface implementation using the method reference and return an empty string Supplier<String> supplier = String::new; Copy the code
Method references are another representation of a Lambda expression
Scenarios where a Lambda expression can be replaced by a method reference: the body of the Lambda expression contains only one expression that calls only one method that already exists
Method references can be:
- Class: : Static methods
- Class: : new
- Class: : instance method ((A, B) -> A. Instance method (B) <=> A class: : Instance method)
- Arbitrary object: : Instance method
Create a Stream
-
Convert a list or array to a stream using the stream method
Arrays.asList("a1"."a2"."a3").stream().forEach(System.out::println); Arrays.stream(new int[] {1.2.3}).forEach(System.out::println); Copy the code
The arrays. asList method of arrays. asList can also be used to convert list and stream to values that aren’t of basic data types
-
A Stream is formed by passing in elements directly through the stream. of method
String[] arr = {"a"."b"."c"}; Stream.of(arr).forEach(System.out::println); Stream.of("a"."b"."c").forEach(System.out::println); Stream.of(1.2."a").map(item -> item.getClass().getName()) .forEach(System.out::println); Copy the code
-
Construct an infinite Stream iteratively with the stream. iterate method, and then use limit to limit the number of elements in the Stream
Stream.iterate(2, item -> item * 2).limit(10).forEach(System.out::println); Stream.iterate(BigInteger.ZERO, n -> n.add(BigInteger.TEN)) .limit(10).forEach(System.out::println); Copy the code
-
An infinite Stream is constructed by passing in an element provider from outside the Stream. Generate method, and then limit the number of elements in the Stream
Stream.generate(() -> "test").limit(3).forEach(System.out::println); Stream.generate(Math::random).limit(10).forEach(System.out::println); Copy the code
-
A stream of the basic type is constructed from an IntStream or DoubleStream
/ / IntStream and DoubleStream IntStream.range(1.3).forEach(System.out::println); IntStream.range(0.3).mapToObj(i -> "x").forEach(System.out::println); IntStream.rangeClosed(1.3).forEach(System.out::println); DoubleStream.of(1.1.2.2.3.3).forEach(System.out::println); // Create a Random stream using the Random class new Random() .ints(1.100) // IntStream .limit(10) .forEach(System.out::println); // Notice the difference between a basic type stream and a boxed stream Arrays.asList("a"."b"."c").stream() // Stream<String> .mapToInt(String::length) // IntStream .asLongStream() // LongStream .mapToDouble(x -> x / 10.0) // DoubleStream .boxed() // Stream<Double> .mapToLong(x -> 1L) // LongStream .mapToObj(x -> "") // Stream<String> .collect(Collectors.toList()); Copy the code
In the middle of operation
Stream API:
The following is the test entity class. The getter, settter, and constructor are omitted
Order items
public class OrderItem {
private Long productId;ID / / commodities
private String productName;// Trade name
private Double productPrice;// The price of commodities
private Integer productQuantity;// Quantity of goods
}
Copy the code
The order
public class Order {
private Long id;
private Long customerId;/ / customer ID
private String customerName;// Customer name
private List<OrderItem> orderItemList;// Order details
private Double totalPrice;/ / total price
private LocalDateTime placedAt;// Order time
}
Copy the code
consumers
public class Customer {
private Long id;
private String name;// Customer name
}
Copy the code
filter
The filter operation acts as a filter, similar to the WHERE condition in SQL, which takes a Predicate Predicate object as an argument and returns the filtered stream
You can use filter consecutively to filter multiple layers
// Search for orders in the last six months with an amount greater than 40
orders.stream()
.filter(Objects::nonNull) // Filter null values
.filter(order -> order.getPlacedAt()
.isAfter(LocalDateTime.now().minusMonths(6))) // Orders for the last six months
.filter(order -> order.getTotalPrice() > 40) // Orders with an amount greater than 40
.forEach(System.out::println);
Copy the code
map
The MAP operation is used for transformations, also called projections, similar to select in SQL
// Count the quantity of all orders
// 1. Achieve this by traversing twice
LongAdder longAdder = new LongAdder();
orders.stream().forEach(order ->
order.getOrderItemList().forEach(orderItem -> longAdder.add(orderItem.getProductQuantity())));
System.out.println("longAdder = " + longAdder);
// 2. Use mapToLong and sum methods twice
long sum = orders.stream().mapToLong(
order -> order.getOrderItemList().stream()
.mapToLong(OrderItem::getProductQuantity)
.sum()
).sum();
Copy the code
flatMap
FlatMap is a flattening operation that replaces each element with a flow using a map and then expands the flow
// Count the total price of all orders
// 1. Directly expand the goods ordered for price statistics
double sum1 = orders.stream()
.flatMap(order -> order.getOrderItemList().stream())
.mapToDouble(item -> item.getProductQuantity() * item.getProductPrice())
.sum();
// 2. Another way to do this is to flatMap + mapToDouble and return DoubleStream
double sum2 = orders.stream()
.flatMapToDouble(order ->
order.getOrderItemList()
.stream().mapToDouble(item ->
item.getProductQuantity() * item.getProductPrice())
)
.sum();
Copy the code
sorted
Sorted is a sort operation that is similar to the ORDER by clause in SQL. It takes a Comparator as an argument. You can use Comparator.comparing to arrange things in large order and reversed to show reverse
// For orders greater than 50, the order price is in the top 5 in reverse order
orders.stream()
.filter(order -> order.getTotalPrice() > 50)
.sorted(Comparator.comparing(Order::getTotalPrice).reversed())
.limit(5)
.forEach(System.out::println);
Copy the code
skip & limit
Skip is used to skip items in the stream, and limit is used to limit the number of items
// Query the names of the first two orders by order time
orders.stream()
.sorted(Comparator.comparing(Order::getPlacedAt))
.map(order -> order.getCustomerName())
.limit(2)
.forEach(System.out::println);
// Query the names of the 3rd and 4th order customers by order time
orders.stream()
.sorted(Comparator.comparing(Order::getPlacedAt))
.map(order -> order.getCustomerName())
.skip(2).limit(2)
.forEach(System.out::println);
Copy the code
distinct
The DISTINCT operation is used to remove weight. It is similar to the DISTINCT operation in SQL
// Remove duplicate order user
orders.stream()
.map(Order::getCustomerName)
.distinct()
.forEach(System.out::println);
// All goods purchased
orders.stream()
.flatMap(order -> order.getOrderItemList().stream())
.map(OrderItem::getProductName)
.distinct()
.forEach(System.out::println);
Copy the code
Put an end to the operation
forEach
As has been used many times above, all elements in the inner loop flow are consumed for each element
ForEachOrder is similar to forEach, but it guarantees the order of consumption
count
Returns the number of items in the stream
toArray
Convert the stream to an array
anyMatch
Short circuit operation, return true if there is one match
// Check whether there is an order with a total price of more than 100 yuan
boolean b = orders.stream()
.filter(order -> order.getTotalPrice() > 50)
.anyMatch(order -> order.getTotalPrice() > 100);
Copy the code
Other short circuit operations:
AllMatch: true if all matches are matched
NoneMatch: returns true if neither match
FindFirst: Returns the Optional package for the first item
FindAny: Returns an Optional wrapper for either of the items. Serial streams typically return the first
reduce
Induction, as you iterate, you keep the results, and you substitute them into the next loop
There are three overloading methods.
// One parameter
// Ask for the total value of the order
Optional<Double> reduce = orders.stream()
.map(Order::getTotalPrice)
.reduce((p, n) -> {
return p + n;
});
// Two parameters
// You can specify an initial value. The initial value type must be the same as p and n
Double reduce2 = orders.stream()
.map(Order::getTotalPrice)
.reduce(0.0, (p, n) -> {
return p + n;
});
Copy the code
Reduce method with three parameters:
You can receive an initial value of the target result type, a serial handler, and a parallel merge function
// Concatenate the customer names for all orders
// The first argument is the target result type, which is set to an empty StringBuilder
// The second argument BiFunction is the last StringBuilder and the next item in the stream, returning the new StringBuilder
// The third argument, BinaryOperator, returns the combined StringBuilder
StringBuilder reduce = orders.stream()
.reduce(new StringBuilder(),
(sb, next) -> {
return sb.append(next.getCustomerName() + ",");
},
(sb1, sb2) -> {
return sb1.append(sb2);
});
Copy the code
Other inductive methods:
Max /min Takes a comparator as an argument
For a base type Stream such as LongStream, no comparator parameter is passed
collect
Collect is a collection operation that terminates a stream and exports the stream into the data structure we need
Collect requires receiving a Collector Collector object as an argument, and the JDK’s built-in Collector implementation class, Collectors, contains many common export methods
Collect Common apis:
Export stream as collection:
- ToList and toUnmodifiableList
// Convert to List (ArrayList)
orders.stream().collect(Collectors.toList());
// Convert to an unmodifiable List
orders.collect(Collectors.toUnmodifiableList());
Copy the code
- ToSet and toUnmodifiableSet
/ / to the Set
orders.stream().collect(Collectors.toSet());
// Convert to an unmodifiable Set
orders.stream().collect(Collectors.toUnmodifiableSet());
Copy the code
- ToCollection specifies a collection type, such as LinkedList
orders.stream().collect(Collectors.toCollection(LinkedList::new));
Copy the code
Export stream as Map:
- ToMap, the third argument, specifies the rule for selecting the key value when the key name is repeated
// Use toMap to obtain the Map of the order ID + the order user name
orders.stream()
.collect(Collectors.toMap(Order::getId, Order::getCustomerName))
.entrySet().forEach(System.out::println);
// Use toMap to get a Map of user name + last order time
orders.stream()
.collect(Collectors.toMap(Order::getCustomerName, Order::getPlacedAt,
(x, y) -> x.isAfter(y) ? x : y))
.entrySet().forEach(System.out::println);
Copy the code
-
ToUnmodifiableMap: Returns an unmodifiable Map
-
ToConcurrentMap: Returns a thread-safe Map
Group Export:
When you encounter duplicate key names in toMap, select a key value to be retained by specifying a handler
In most cases, we need to group maps by key names, and the oughters. groupingBy is a better choice
There are three overloading methods.
One parameter is equivalent to the second parameter, which is of the item. toList type
// Group according to the order user name, and the key value is the order List corresponding to the customer
Map<String, List<Order>> collect = orders.stream()
.collect(Collectors.groupingBy(Order::getCustomerName));
Copy the code
Two arguments. The second argument specifies the key value type
// Group by user name, and the key value is the number of orders for that customer
Map<String, Long> collect = orders.stream()
.collect(Collectors.groupingBy(Order::getCustomerName,
Collectors.counting()));
Copy the code
The second parameter specifies the Map type of the grouping result, and the third parameter specifies the key value type
// Group according to the order user name, and the key value is the total price of all goods corresponding to the customer
Map<String, Double> collect = orders.stream()
.collect(Collectors.groupingBy(Order::getCustomerName,
Collectors.summingDouble(Order::getTotalPrice)));
// Specify the Map type of the grouping result as TreeMap
Map<String, Double> collect = orders.stream()
.collect(Collectors.groupingBy(Order::getCustomerName,
TreeMap::new,
Collectors.summingDouble(Order::getTotalPrice)));
Copy the code
Partition export:
Partition using Collectors partitioningBy, is to group the data according to the TRUE or FALSE
// Partition according to whether there is a single record
customers.stream()
.collect(Collectors.partitioningBy(customer -> orders
.stream()
.mapToLong(Order::getCustomerId)
.anyMatch(id -> id == customer.getId())
));
/ / equivalent to the
customers.stream()
.collect(Collectors.partitioningBy(customer -> orders
.stream()
.filter(order -> order.getCustomerId() == customer.getId())
.findAny()
.isPresent()
));
Copy the code
Class intermediate operations:
Collectors also provide apis similar to intermediate operations for collection, such as Counting, summingDouble, and maxBy
Collectors.maxBy
// Obtain the goods with the largest order volume, three ways
// Use the collector maxBy
Map.Entry<String, Integer> e1 = orders.stream()
.flatMap(order -> order.getOrderItemList().stream())
.collect(Collectors.groupingBy(OrderItem::getProductName,
Collectors.summingInt(OrderItem::getProductQuantity)))
.entrySet()
.stream()
.collect(Collectors.maxBy(Map.Entry.<String, Integer>comparingByValue()))
.get();
// Use the intermediate operation Max
Map.Entry<String, Integer> e2 = orders.stream()
.flatMap(order -> order.getOrderItemList().stream())
.collect(Collectors.groupingBy(OrderItem::getProductName,
Collectors.summingInt(OrderItem::getProductQuantity)))
.entrySet()
.stream()
.max(Map.Entry.<String, Integer>comparingByValue())
.get();
// Order from largest to smallest, then findFirst
Map.Entry<String, Integer> e3 = orders.stream()
.flatMap(order -> order.getOrderItemList().stream())
.collect(Collectors.groupingBy(OrderItem::getProductName,
Collectors.summingInt(OrderItem::getProductQuantity)))
.entrySet()
.stream()
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
.findFirst()
.get();
/ / note:
// Map.Entry.<String, Integer>comparingByValue().reversed()
// Can be equivalent to the following statement
// Comparator.comparing(Map.Entry<String, Integer>::getValue).reversed()
Copy the code
Collectors.joining
// Delete the order user name after splicing
String collect = orders.stream()
.map(Order::getCustomerName)
.distinct()
.collect(Collectors.joining(","));
// .collect(Collectors.joining(",", "[", "]")); // Prefix and suffix can be specified
Copy the code