Using Java 8 Stream to write code, clean and elegant!

Keywords: Java

The new features of Java 8 are mainly Lambda expressions and streams. When streams and Lambda expressions are used together, the code can become concise and easy to read because of the characteristics of stream declarative processing of data sets

1 how to simplify code flow

If there is a requirement, you need to process the dishes queried in the database:

  • Select dishes with less than 400 calories
  • Sort the selected dishes
  • Gets the name of the sorted dish

Dish: Dish.java

public class Dish {
    private String name;
    private boolean vegetarian;
    private int calories;
    private Type type;
    // getter and setter
}
Copy code

Previous implementations of Java 8

private List<String> beforeJava7(List<Dish> dishList) {
    List<Dish> lowCaloricDishes = new ArrayList<>();

    //1. Select dishes with less than 400 calories
    for (Dish dish : dishList) {
        if (dish.getCalories() < 400) {
            lowCaloricDishes.add(dish);
        }
    }

    //2. Sort the selected dishes
    Collections.sort(lowCaloricDishes, new Comparator<Dish>() {
        @Override
        public int compare(Dish o1, Dish o2) {
            return Integer.compare(o1.getCalories(), o2.getCalories());
        }
    });

    //3. Get the name of the dishes after sorting
    List<String> lowCaloricDishesName = new ArrayList<>();
    for (Dish d : lowCaloricDishes) {
        lowCaloricDishesName.add(d.getName());
    }

    return lowCaloricDishesName;
}
Copy code

Implementation after Java 8

private List<String> afterJava8(List<Dish> dishList) {
    return dishList.stream()
            .filter(d -> d.getCalories() < 400) //Select dishes with less than 400 calories
            .sorted(comparing(Dish::getCalories)) //Sort by calories
            .map(Dish::getName) //Extract dish name
            .collect(Collectors.toList()); //Convert to List
}
Copy code

It's done at one go without procrastination. The functions that originally needed to be realized by writing 24 code can now be completed in only 5 lines

After writing the requirements happily, there are new requirements at this time. The new requirements are as follows:

Classify the dishes found in the database according to the type of dishes, and return a map < type, list >

If you put it jdk8 before, it would make your scalp numb

Previous implementations of Java 8

private static Map<Type, List<Dish>> beforeJdk8(List<Dish> dishList) {
    Map<Type, List<Dish>> result = new HashMap<>();

    for (Dish dish : dishList) {
        //Initialize if not present
        if (result.get(dish.getType())==null) {
            List<Dish> dishes = new ArrayList<>();
            dishes.add(dish);
            result.put(dish.getType(), dishes);
        } else {
            //Append if it exists
            result.get(dish.getType()).add(dish);
        }
    }

    return result;
}
Copy code

Fortunately jdk8, there is a Stream, so you don't have to worry about complex collection processing requirements anymore

Implementation after Java 8

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

Another line of code solves the demand. I can't help shouting Stream API. See the powerful functions of stream. Next, I'll introduce stream in detail

2 what is flow

A stream is a sequence of elements generated from a source that supports data processing operations. The source can be an array, a file, a collection, or a function. Stream is not a collection element, it is not a data structure and does not save data. Its main purpose is to calculate

3 how to generate flow

There are five main ways to generate streams

1. It is generated through collection, which is the most commonly used one in applications

List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> stream = integerList.stream();
Copy code

Generate a stream through the stream method of the collection

2. Generate by array

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

The Stream is generated by the Arrays.stream method, and the Stream generated by this method is a numerical Stream [i.e. IntStream] rather than a Stream. In addition, the use of numerical flow can avoid unpacking in the calculation process and improve the performance.

The Stream API provides mapToInt, mapToDouble and mapToLong to convert the object Stream [i.e. Stream] into the corresponding numerical Stream, and provides the boxed method to convert the numerical Stream into the object Stream

3. Generate by value

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
Copy code

The Stream is generated by the of method of Stream, and an empty Stream can be generated by the empty method of Stream

4. Generated by file

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

Get a stream through the Files.line method, and each stream is a line in the given file

5. Two static methods, iterate and generate, are provided to generate streams from functions through function generation

iterator

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

The iterate method accepts two parameters. The first is the initialization value and the second is the function operation. Because the stream generated by the iterator is infinite, the stream is truncated by the limit method, and only 5 even numbers are generated

generator

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

The generate method accepts a parameter of type Supplier, which provides a value for the flow. The stream generated by generate is also an infinite stream, so it is truncated by limit convection

4. Operation type of flow

There are two main types of operations for streams

1. Intermediate operation

A flow can be followed by zero or more intermediate operations. Its main purpose is to open the flow, make some degree of data mapping / filtering, and then return a new flow for the next operation. Such operations are inert. Only calling such methods does not really start the flow traversal. The real traversal needs to wait until the terminal operation. Common intermediate operations include filter and map, which will be introduced below

2. Terminal operation

A stream has and can only have one terminal operation. After this operation is executed, the stream is closed and can no longer be operated. Therefore, a stream can only be traversed once. If you want to traverse, you need to generate a stream through the source data. Only when the terminal operation is executed can the flow traversal really begin. Such as count and collect, which will be introduced later

5 stream usage

The use of stream will be divided into terminal operation and intermediate operation

Intermediate operation

Filter filter

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

By using the filter method to filter conditions, the filter method parameter is a condition

distinct remove duplicate elements

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

Rapid removal of duplicate elements by distinct method

limit returns the number of specified streams

List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5);
Stream<Integer> stream = integerList.stream().limit(3);
Copy code

Specify the number of return streams through the limit method. The parameter value of limit 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 code

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

map stream mapping

The so-called flow mapping is to map the accepted element to another element

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

The mapping can be completed through the map method. In this example, the mapping of string - > integer is completed. In the previous example, the mapping of dish - > string is completed through the map method

flatMap stream 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(" "))
        .flatMap(Arrays::stream)
        .distinct()
        .collect(Collectors.toList());
Copy code

The return value of map (W - > w.split ("")) is Stream < string [] >. If we want to get the Stream, we can complete the conversion of Stream - > Stream through the flatMap method

Element matching

Three matching methods are provided

1.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 are greater than 3");
}
Copy code

Realized by allMatch method

2.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 is a value greater than 3");
}
Copy code

Equivalent to

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

If there is a value greater than 3, it will print. This function is realized by anyMatch method in Java 8

3. None matches

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 code

It is implemented by the nonmetch method

6 terminal operation

Count the number of elements in the stream

1. Through count

List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
Long result = integerList.stream().count();
Copy code

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

2. Through counting

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

The last method of counting the number of elements is particularly useful when used in conjunction with collect

lookup

Two search methods are provided

1.findFirst find the first

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

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

2.findAny randomly finds one

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

Find and print one of the elements greater than three through findAny method. For internal optimization reasons, it ends when the first element satisfying greater than three is found. The result of this method is the same as that of findFirst method. The findAny method is provided to make better use of parallel streams. The findFirst method has more restrictions on parallelism

reduce combines the elements in the flow

Suppose we sum the values in a set

Before jdk8

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

jdk8 is processed through reduce

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

It can be completed in one line, and the method reference can also be abbreviated as:

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

reduce accepts two parameters, an initial value here is 0, 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 and maximum values in the stream

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 code

It can also be written as:

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

min gets the minimum value in the stream, max gets the maximum value in the stream, and the method parameter is comparator <? super T> comparator

Get the minimum and maximum value through 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 code

minBy gets the minimum value in the stream, maxBy gets the maximum value in the stream, and the method parameter is comparator <? super T> comparator

Get the minimum and maximum values 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 code

7 Summary

By using Stream API, you can simplify the code, improve the readability of the code, and quickly use it in the project

Posted by p2003morris on Sun, 21 Nov 2021 13:24:51 -0800