This is the ninth day of my participation in the August More text Challenge. For details, see: August More Text Challenge

Java 8 introduced

  1. Java 8 was released in 2014 and, years later, is still the most commonly used JDK version
  2. 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
  3. The combination of Lambda expressions with Stream operations has greatly improved the efficiency of our daily coding

Lambda expressions

  1. Anonymous class inner classes do not have class names, but they still give method definitions

  2. The purpose of Lambda expressions is to further simplify the syntax of anonymous classes

  3. In implementation, Lambda expressions are not syntactic sugar for anonymous classes

  4. 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
  5. 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

  1. 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

  2. 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
  3. 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
  4. 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
  5. 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:

  1. ToList and toUnmodifiableList
// Convert to List (ArrayList)
orders.stream().collect(Collectors.toList());
// Convert to an unmodifiable List
orders.collect(Collectors.toUnmodifiableList());
Copy the code
  1. ToSet and toUnmodifiableSet
/ / to the Set
orders.stream().collect(Collectors.toSet());
// Convert to an unmodifiable Set
orders.stream().collect(Collectors.toUnmodifiableSet());
Copy the code
  1. ToCollection specifies a collection type, such as LinkedList
orders.stream().collect(Collectors.toCollection(LinkedList::new));
Copy the code

Export stream as Map:

  1. 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
  1. ToUnmodifiableMap: Returns an unmodifiable Map

  2. 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