New to Java8 are Lambda expressions and streams. When used together, streams and Lambda expressions can make code concise and readable because of the declarative nature of streams that handle data sets

How does flow simplify code

If there is a requirement to perform a processing on the dishes found in the database:

  • Screen for dishes with fewer than 400 calories
  • Make a ranking of the selected dishes
  • Gets the name of the sorted dish
Cuisine: Dish. Java
public class Dish {
    private String name;
    private boolean vegetarian;
    private int calories;
    private Type type;

    // getter and setter
}
Copy the code
How Java8 was implemented before
private List<String> beforeJava7(List<Dish> dishList) { List<Dish> lowCaloricDishes = new ArrayList<>(); //1. Select dishes with fewer than 400 caloriesfor (Dish dish : dishList) {
            if(dish.getCalories() < 400) { lowCaloricDishes.add(dish); Sort (lowCaloricDishes, new Comparator<Dish>() {@override public int compare(Dish o1, Dish o2) {returnInteger.compare(o1.getCalories(), o2.getCalories()); }}); List<String> lowCaloricDishesName = new ArrayList<>();for (Dish d : lowCaloricDishes) {
            lowCaloricDishesName.add(d.getName());
        }
        
        return lowCaloricDishesName;
    }
Copy the code
Implementation after Java8
 private List<String> afterJava8(List<Dish> dishList) {
        returnDishlist.stream ().filter(d -> d. calorie () < 400) // Select dishes with calories less than 400. Sorted (Comparing (Dish:: getcalorie)) // Sort by calories .map(Dish::getName) // Collect (Collectors. ToList ()); // Convert to List}Copy the code

It’s easy to do things in five lines that used to take 24 lines of code

  • Classify the dishes queried in the database according to the types of dishes and return oneMap<Type, List<Dish>>The results of the

This would have been creepy before JDK8

How Java8 was implemented before
private static Map<Type, List<Dish>> beforeJdk8(List<Dish> dishList) {
    Map<Type, List<Dish>> result = new HashMap<>();

    for(Dish Dish: dishList) {// Initialize if not presentif (result.get(dish.getType())==null) {
            List<Dish> dishes = new ArrayList<>();
            dishes.add(dish);
            result.put(dish.getType(), dishes);
        } else{// add result.get(dish.getType()).add(dish); }}return result;
}
Copy the code

Fortunately, JDK8 has streams, so you don’t have to worry about complex collection handling requirements anymore

Java8 after the implementation
private static Map<Type, List<Dish>> afterJdk8(List<Dish> dishList) {
    return dishList.stream().collect(groupingBy(Dish::getType));
}
Copy the code

See the power of streams. We’ll cover streams in more detail

What is the flow

A stream is a sequence of elements generated from a source that supports data processing operations. The sources can be arrays, files, collections, or functions. A stream is not a collection element, it is not a data structure, it does not hold data, and its primary purpose is computation

How to generate a stream

There are five main ways to generate streams

  • Through collection generation, one of the most common applications
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> stream = integerList.stream();
Copy the code

A stream is generated through the stream method of the collection

  • From an array
    int[] intArr = new int[]{1, 2, 3, 4, 5};
    IntStream stream = Arrays.stream(intArr);
    Copy the code

The arrays. stream method generates streams that are numeric streams rather than stream

. The use of numerical flow can avoid unpacking during calculation and improve performance. The Stream API provides mapToInt, mapToDouble, and mapToLong methods for converting a Stream into a numeric Stream. It also provides a boxed method for converting a numeric Stream into an object Stream

  • By value generation
    Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
    Copy the code

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

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

A stream is obtained using the files.line method, and each resulting stream is a row in a given file

  • Provided through function generationiterateandgenerateTwo static methods generate streams from functions
    • iterator
    Stream<Integer> stream = Stream.iterate(0, n -> n + 2).limit(5);
    Copy the code

    iterateThe method takes two arguments, the first being the initialization value and the second being the function operation to perform, becauseiteratorThe generated stream is an infinite stream and passeslimitMethods The flow is truncated and only 5 even numbers are generated

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

    generateThe method takes one parameter of typeSupplier<T>, which provides the value for the stream.generateThe resulting stream is also infinite and therefore passeslimitThe convection was truncated

The operation type of the flow

There are two main types of operations for streams

  • Intermediate operations A stream can be followed by zero or more intermediate operations. The main purpose is to open the stream, do some degree of data mapping/filtering, and then return a new stream to be used by the next operation. These types of operations are inert, and simply calling these methods does not actually start the stream traversal. The actual traversal will have to wait for terminal operations. Common intermediate operations are described belowfilter,mapEtc.
  • Terminal operation A stream can have only one terminal operation. After this operation is performed, the stream is closed and cannot be operated again. Therefore, a stream can only be traversed once. It is the execution of the terminal operation that really begins the stream traversal. As will be described belowcount,collectEtc.

Flow using

The use of streams will be divided into terminal operations and intermediate operations

In the middle of operation

The filter screen
 List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5);
 Stream<Integer> stream = integerList.stream().filter(i -> i > 3);
Copy the code

Filter method is used to filter conditions. The parameter of filter method is a condition

Distinct Removes duplicate elements
List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5);
Stream<Integer> stream = integerList.stream().distinct();
Copy the code

Use the DISTINCT method to quickly remove duplicate elements

Limit Returns the specified number of streams
 List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5);
 Stream<Integer> stream = integerList.stream().limit(3);
Copy the code

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

Skip Skips 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 by skipping the first two elements in the example above, so it prints 2,3,4,5. Skip must be >=0, otherwise an exception will be thrown

The map flow mapping

A flow map is a 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

The mapping can be done using the map method. This example completes the mapping of String -> Integer. The previous example completed the mapping of Dish->String using the map method

FlatMap flow conversion

Converts every value in one stream to another

List<String> wordList = Arrays.asList("Hello"."World");
List<String> strList = wordList.stream()
        .map(w -> w.split(""))
        .flatMap(Arrays::stream)
        .distinct()
        .collect(Collectors.toList());
Copy the code

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

. If we want to get Stream

, we can convert Stream

->Stream

to flatMap

[]>

[]>

Match elements

Three matching methods are provided

  • AllMatch matches all
    List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
    if (integerList.stream().allMatch(i -> i > 3)) {
        System.out.println("All values greater than 3");
    }
    Copy the code

Through allMatch method

  • AnyMatch matches one of them
    List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
    if (integerList.stream().anyMatch(i -> i > 3)) {
        System.out.println("There are values greater than 3.");
    }
    Copy the code

    Is equivalent to

    for (Integer i : integerList) {
        if (i > 3) {
            System.out.println("There are values greater than 3.");
            break; }}Copy the code

Prints if there is a value greater than 3, which is implemented in java8 through the anyMatch method

  • NoneMatch all don’t match
    List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
    if (integerList.stream().noneMatch(i -> i > 3)) {
        System.out.println("All values less than 3");
    }
    Copy the code

    throughnoneMatchMethod implementation

Terminal operation

Count the number of elements in the flow
  • By the count
    List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
    Long result = integerList.stream().count();
    Copy the code

Count the number of elements in the stream by using the count method

  • Through counting
    List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
    Long result = integerList.stream().collect(counting());
    Copy the code

This last method of counting the number of elements is particularly useful when used in conjunction with COLLECT

To find the

There are two ways to find it

  • FindFirst finds the first one

    List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
    Optional<Integer> result = integerList.stream().filter(i -> i > 3).findFirst();
    Copy the code

    Find the first element greater than three using the findFirst method and print it

  • FindAny Finds a random one

    List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
    Optional<Integer> result = integerList.stream().filter(i -> i > 3).findAny();
    Copy the code

    The findAny method finds one element greater than three and prints it. For internal optimization reasons, it ends when it finds the first element greater than three, which gives the same result as the findFirst method. The findAny method is provided to make better use of parallel streams. The findFirst method is more limited in parallelism.

Reduce groups elements in the flow

Suppose we sum the values of a set

  • Jdk8 before

    int sum = 0;
    for (int i : integerList) {
    sum += i;
    }
    Copy the code
  • Jdk8 is processed by reduce

    int sum = integerList.stream().reduce(0, (a, b) -> (a + b));
    Copy the code

    This can be done in a single line, or you can use the method reference shorthand:

    int sum = integerList.stream().reduce(0, Integer::sum);
    Copy the code

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

    Accumulator to combine the two elements to produce a new value, as well as an overloaded method with no initialization value

Gets the minimum maximum value in the stream
  • Obtain the minimum and maximum value by min/ Max
    Optional<Integer> min = menu.stream().map(Dish::getCalories).min(Integer::compareTo);
    Optional<Integer> max = menu.stream().map(Dish::getCalories).max(Integer::compareTo);
    Copy the code

    It can also be written as:

    OptionalInt min = menu.stream().mapToInt(Dish::getCalories).min();
    OptionalInt max = menu.stream().mapToInt(Dish::getCalories).max();
    Copy the code

    minGet the minimum value in the stream,maxTo obtain the maximum value in the stream, the method parameter isComparator<? super T> comparator

  • Get the minimum and maximum values by minBy/maxBy
    Optional<Integer> min = menu.stream().map(Dish::getCalories).collect(minBy(Integer::compareTo));
    Optional<Integer> max = menu.stream().map(Dish::getCalories).collect(maxBy(Integer::compareTo));
    Copy the code

    minByGet the minimum value in the stream,maxByTo obtain the maximum value in the stream, the method parameter isComparator<? super T> comparator

  • Obtain the minimum and maximum value through reduce
    Optional<Integer> min = menu.stream().map(Dish::getCalories).reduce(Integer::min);
    Optional<Integer> max = menu.stream().map(Dish::getCalories).reduce(Integer::max);
    Copy the code
sum
  • Through summingInt
    int sum = menu.stream().collect(summingInt(Dish::getCalories));
    Copy the code

    If the data type isdouble,longbysummingDouble,summingLongMethod to sum

  • Through the reduce
    int sum = menu.stream().map(Dish::getCalories).reduce(0, Integer::sum);
    Copy the code
  • Through the sum
    int sum = menu.stream().mapToInt(Dish::getCalories).sum();
    Copy the code

When summing, maximizing, and minimizing above, there are different ways to choose to perform the same operation. The methods can be Collect, reduce, and min/ Max /sum. The min, Max, and sum methods are recommended. Because it is the most concise and readable, mapToInt converts the object stream to a numeric stream, avoiding boxing and unboxing operations

Average by averagingInt
double average = menu.stream().collect(averagingInt(Dish::getCalories));
Copy the code

If the data type is double and long, averagingDouble and averagingLong are used to average

Summation, average, maximum, and minimum values are computed by summarizingInt
IntSummaryStatistics intSummaryStatistics = menu.stream().collect(summarizingInt(Dish::getCalories)); double average = intSummaryStatistics.getAverage(); / / get average int min = intSummaryStatistics. GetMin(a); / / get the minimum int Max = intSummaryStatistics. GetMax (); / / get the maximum long sum = intSummaryStatistics. GetSum (); // Get the sumCopy the code

If the data type is summarizingDouble or summarizingLong, the summarizingDouble or summarizingLong methods are used

Element traversal via foreach
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
integerList.stream().forEach(System.out::println);
Copy the code

While implementing traversal before JDK8:

for (int i : integerList) {
    System.out.println(i);
}
Copy the code

In jdk8, iterating through elements is much easier. For-each can be implemented directly through foreach

Returns the collection
List<String> strings = menu.stream().map(Dish::getName).collect(toList());
Set<String> sets = menu.stream().map(Dish::getName).collect(toSet());
Copy the code

Just a few examples, there are many other methods before JDK8

 List<String> stringList = new ArrayList<>();
    Set<String> stringSet = new HashSet<>();
    for (Dish dish : menu) {
        stringList.add(dish.getName());
        stringSet.add(dish.getName());
}
Copy the code

The use of traversal and return collections to discover that a flow simply brings the original external iteration inside, which is one of the main features of a flow. Internal iteration can reduce a lot of code

Joining elements in a stream through joining
String result = menu.stream().map(Dish::getName).collect(Collectors.joining(","));
Copy the code

By default, if the map method is not used to process the string returned by the toString method of joining, the parameter of the joining method is the delimiter of the element. If the string is not specified, it will be a string, which is not readable

Advancements are grouped by groupingBy
Map<Type, List<Dish>> result = dishList.stream().collect(groupingBy(Dish::getType));
Copy the code

GroupingBy is passed into the collect method for grouping, where the method parameter of groupingBy is the classification function. You can also use groupingBy for multiple levels of classification by nesting

Map<Type, List<Dish>> result = menu.stream().collect(groupingBy(Dish::getType,
        groupingBy(dish -> {
            if (dish.getCalories() <= 400) return CaloricLevel.DIET;
                else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
                else return CaloricLevel.FAT;
        })));
Copy the code
Advanced partitioning is done by partitioningBy

Partitions are special groups that are classified according to true and false, so the results returned can be divided into up to two groups

Map<Boolean, List<Dish>> result = menu.stream().collect(partitioningBy(Dish :: isVegetarian))
Copy the code

Is equivalent to

Map<Boolean, List<Dish>> result = menu.stream().collect(groupingBy(Dish :: isVegetarian))
Copy the code

This example may not tell the difference between partitioning and sorting, and may not even be necessary at all.

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 returned value is still of Boolean type, but it is classified by range. Partitioning is better for handling sorting by range

conclusion

By using the Stream API, you can simplify your code and improve the readability of your code. Before I learned the Stream API, I wanted to kick anyone who wrote a lot of lambdas in my application. I think I might be in love with him now. Don’t mix declarative programming with imperative programming. A few days ago, brush segment:

imango
Don’t use declarative programming syntax to do imperative programming