Stream, a new feature of java8

Posted by VirtualOdin on Fri, 04 Mar 2022 07:15:33 +0100

The beginning of experience

First, define a dish class:

  • Dish

public class Dish {
    private final String name;
    private final boolean vegetarian;
    private final int calories;
    private final Type type;
    
    public boolean isVegetarian() {
        return vegetarian;
    }
    
    //Omit the set, get, toString methods
}

Then create a static method and set it as a member variable of the class for testing.

public static List<Dish> getDishes() {
    return Arrays.asList(
        new Dish("pork"false800, Dish.Type.MEAT),
        new Dish("beef"false700, Dish.Type.MEAT),
        new Dish("chicken"false400, Dish.Type.MEAT),
        new Dish("french fries"true530, Dish.Type.OTHER),
        new Dish("rice"true350, Dish.Type.OTHER),
        new Dish("pizza"true550, Dish.Type.OTHER),
        new Dish("prawns"false300, Dish.Type.FISH),
        new Dish("salmon"false450, Dish.Type.FISH)
    );
}

XNBqZ

Well, now there's a need to find all the foods with less than 400 calories in the dish and sort them according to the size of calories.

Before java8, even after java8, some people would think about using an intermediate variable to ensure the dishes that meet the requirements, and then sort them.

public static List<String> beforeJava8() {
    List<Dish> lowCaloricDishes = new ArrayList<>();
    for (Dish dish : dishes) {
        if (dish.getCalories() < 400) {
            lowCaloricDishes.add(dish);
        }
    }

    lowCaloricDishes.sort(Comparator.comparingInt(Dish::getCalories));
//    lowCaloricDishes.sort((d1, d2) -> Integer.compare(d1.getCalories(), d2.getCalories()));
    List<String> res = new ArrayList<>();

    for (Dish dish : lowCaloricDishes) {
        res.add(dish.getName());
    }
    return res;
}

Since the previous article talked about method reference, it is used directly here, but the following line also has the writing of ordinary Lambda expressions.

Is there any problem with the above writing method? It can be found that lowCaloricDishes is only used once, which is really a temporary variable. Can you skip the process of creating variables? You can give me the data directly. I can get what I want after filtering and sorting, just like the pipeline.

public static List<String> afterJava8() {
    return dishes.stream()
        .filter(dish -> dish.getCalories() < 400)
        .sorted(Comparator.comparing(Dish::getCalories))
        .map(Dish::getName)
        .collect(Collectors.toList());
}

img

This is the Stream to be talked about in this article

Definition of flow

A sequence of elements generated from a source that supports data processing operations

A stream is somewhat similar to a set. A set is a data structure whose main purpose is to store and access elements, while the main purpose of a stream is to perform a series of operations on elements.

Generally speaking, collection is equivalent to downloading a movie, and streaming is equivalent to watching online. In fact, you only need to think of the flow as a high-level collection. Flow has two important characteristics:

  • Pipeline: many streams themselves will return a stream, so that multiple streams can be linked, just like a pipeline.

  • Internal iteration: internal iteration is to encapsulate the iteration, such as collect(Collectors.toList), and the corresponding external iteration is for each.

It is worth noting that, like iterators, a stream can only be traversed once. After traversal, it can be said that the stream is consumed.

Construction of flow

Construction of flow

There are four common ways to build a Stream. In fact, it is either with the static method of Stream class or with the static method of someone else's class.

  • Create flow from value

  • Create stream from array

  • Generate stream from file

  • Stream generated by function

public static void buildStream() throws IOException {
    Stream<String> byValue = Stream.of("java8""c++""go");

    Stream<Object> empty = Stream.empty();

    int[] nums = {12345};
    IntStream byInts = Arrays.stream(nums);

    Stream<String> byFiles = Files.lines(Paths.get(""));

    Stream<Integer> byFunction1 = Stream.iterate(0, n -> n * 2);
    Stream<Double> byFunction2 = Stream.generate(Math::random);

    Stream<String> java = Stream.of("java");
}

Operation of flow

The stream operation that can be connected is called intermediate operation, and the operation of closing the stream is called terminal operation

Generally speaking, an operation that returns a stream is called an intermediate operation, and an operation that returns a stream other than a stream is called a terminal operation.

image-20210414155605342

By looking up the java8 interface, you can know which interfaces are intermediate operations and which interfaces are terminal operations. Because those interfaces are described too officially, it is estimated that no one will read them carefully when I post them, so go directly if you want to see them official Check it.

Use of stream

Let's talk about it according to the order of Java APIs on the official website. I haven't learned the flow before. It's mainly because I feel that there will be many interfaces. How can I remember so much? In fact, I found that there are really few in recent days. Basically, I don't need to remember.

img

First build a list of numbers:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 5, 5, 5, 6, 7);

Intermediate operation

Intermediate operations include de duplication, filtering, truncation, viewing, skipping and sorting. I believe everyone can understand what they mean.

public static void midOperation() {
    numbers.stream()
        .distinct()
        .forEach(System.out::println);

    List<Integer> filter = numbers.stream()
        .filter(n -> n % 2 == 0)
        .collect(Collectors.toList());

    numbers.stream()
        .limit(3)
        .forEach(System.out::println);

    numbers.stream()
        .peek(integer -> System.out.println("consume operation:" + integer))
        .forEach(System.out::println);

    List<Integer> skip = numbers.stream()
        .skip(2)
        .collect(Collectors.toList());

    numbers.stream()
        .sorted()
        .forEach(System.out::println);
}

Mapping of intermediate operations

What needs to be mentioned separately are map and flatmap. Note that the map here is not the map of hashmap, but what is mapped or transformed into.

public static void midOperation() {
 List<String> map = numbers.stream()
                .map(Object::toString)   //Here we map int to string
                .collect(Collectors.toList());
}

For flat mapping, there is another requirement. Now there is a word list, such as {"hello", "world"}, which returns different characters, that is, it is required to return list < string >.

It's not easy. Just map the words into letters and repeat them.

public static void flatMapDemoNoral() {
    List<String> words = Arrays.asList("hello""world");
    List<String[]> normal = words.stream()
        .map(str -> str.split(""))
        .distinct()
        .collect(Collectors.toList());
}

img

Although the effect can be achieved, note that the function used for mapping is split(), which returns String [], so the whole return is list < String [] >

Then I will map each String [] array into a stream after mapping

public static void flatMapDemoMap() {
    List<String> words = Arrays.asList("hello""world");
    List<Stream<String>> usingMap = words.stream()
                .map(str -> str.split(""))
                .map(Arrays::stream)
                .distinct()
                .collect(Collectors.toList());
}

Although the hat of array is removed, list < stream < string > > is returned.

flatMap is designed to solve this situation

public static void flatMapDemoFlatMap() {
    List<String> words = Arrays.asList("hello""world");
    List<String> usingFlatMap = words.stream()
                .map(str -> str.split(""))
                .flatMap(Arrays::stream)
                .distinct()
                .collect(Collectors.toList());
}

It can be simply understood that a map maps each element into an independent stream, while a flattened map saves the elements and finally maps them into a stream.

Find and match

In addition to the collection () and forEach() commonly used in the above example writing, the terminal operation also has two major directions: search and specification.

Because there's nothing to say, just go straight to the code:

public static void endOperationFindAndMatch() {
    if (dishes.stream().noneMatch(Dish::isVegetarian)) {
        System.out.println("All dishes are non vegetarian");
    }
    if (dishes.stream().allMatch(Dish::isVegetarian)) {
        System.out.println("All the dishes are vegetarian");
    }
    if (dishes.stream().anyMatch(Dish::isVegetarian)) {
        System.out.println("At least one of the dishes is vegetarian");
    }

    Optional<Dish> any = dishes.stream()
        .filter(meal -> meal.getCalories() <= 1000)
        .findAny();
    Optional<Dish> first = dishes.stream()
        .filter(meal -> meal.getCalories() <= 1000)
        .findFirst();
}

Reduction (calculation)

For the protocol operation of convection, there are generally ordinary operations, that is, those that can directly call the interface, and another is with the help of reduce().

For ordinary operations, such as summation, maximum value and minimum value, there are interfaces corresponding to them.

public static void endOperationCalculate() {
    long count = dishes.stream()
        .filter(meal -> meal.getCalories() <= 1000)
        .count();
    Optional<Dish> max = dishes.stream()
        .max(Comparator.comparingInt(Dish::getCalories));
    Optional<Dish> min = dishes.stream()
        .min(Comparator.comparing(Dish::getName));

}

But if you want to sum elements, you need to use reduce()

image-20210417114209123

Generally, two parameters can be accepted, one is the initial value, and the other is binaryoprator < T > to combine the two elements to produce a new value.

public static void reduceDemo() {
    List<Integer> numbers = Arrays.asList(1234555567);
    Integer sum = numbers.stream().reduce(0, Integer::sum);
    
    //Multiplication of all elements is also relatively simple
    Integer multi = numbers.stream().reduce(0, (a, b) -> a * b);
    
    //And find the maximum
    Optional<Integer> max = numbers.stream().reduce(Integer::max);
}

Optional class

The return value shown above is optional < T >, which is a container class and represents the existence or non existence of a value. For example, findAny() at the beginning may not find qualified dishes. The main purpose of the introduction of Java 8 is / in order not to return null that is prone to problems.

Just say a few commonly used APIs. As for others, you can check the official APIs online. There are enough APIs today

  • isPresent() will return true when Optional contains a value, otherwise it will return false

  • If ifpresent (consumer < T > block) has a value, the given code block will be executed

  • get() returns a value if there is a value. Otherwise, a NoSuchElement exception is thrown

  • orElse() returns if there is a value, otherwise it returns a default value

public static void optionalDemo() {
    //ifPresent
    dishes.stream()
        .filter(Dish::isVegetarian)
        .findAny()
        .ifPresent(dish -> System.out.println(dish.getName()));

    //isPresent
    boolean isLowCalories= dishes.stream()
        .filter(dish -> dish.getCalories() <= 1000)
        .findAny()
        .isPresent();
 
    //get
    Optional<Dish> optional = dishes.stream()
        .filter(Dish::isVegetarian)
        .findAny();
    if (optional.isPresent()) {
        Dish dish = optional.get();
    }

    //orElse
    Dish dishNormal = dishes.stream()
        .filter(Dish::isVegetarian)
        .findAny()
        .orElse(new Dish("java"false10000, Dish.Type.OTHER));
}

summary

Similarly, there are still a few small points not mentioned, such as the parallel flow part and the collector part, but these are relatively in-depth. After two more articles, I have learned as much about the new features of Java 8 as possible. After digestion, you can go to see "java8 actual combat". Reading is the focus of learning new knowledge, and finally the application.

Topics: Java optional