[Java11] Stream Summary -Advantages of Stream-

-[Java11] Stream Summary -Advantages of Stream- -[Java11] Stream Usage Summary -Basics-

What is Stream

Map to collection-A class that supports functional operations on a stream of elements, such as reduce transforms. Source: Official Document

First of all, let's actually see

Partially modified from Official Reference

stream.java


final int sum = widgets.stream()
                       .filter(w -> w.getColor() == RED)
                       .mapToInt(w -> w.getWeight())
                       .sum();

In the above example, widgets is extracted from widgets with color`` RED and the sum of the values of weight is calculated. If you write it in a for statement, it will be as follows.

for statement.java


int sum = 0;
for (Widget w: widgets) {
    if (w.getColor() == RED) {
        sum += w.getWeight();
    }
}

What can i do?

As I wrote at the beginning, Stream is "* a class that supports functional operations on a stream of elements, such as map-reduce conversion for collections. *"

--Map for collection-Reduce conversion

Roughly speaking, it is ** what I used to do with for statements **. You can calculate the total value above, get the maximum value, create a new list, and so on.

--Supports functional operations

Many of the Stream class methods take a functional interface as an argument. For example, pass ** how to filter ** to the filter function and ** how to convert to integer type ** to the mapToInt function. Stream has a lot of methods that return a new Stream, and you can pass the process to it for flexible processing.

What is good?

I think there are two major merits:

  1. Improved readability
  2. Performance improvement by parallel processing

Improved readability

This is because the writing method using Strean is ** "declarative" ** compared to normal code. Pay particular attention to the part of the above example where the sum (sum) is calculated.

--For Stream Just calling sum (). I'm just saying ** "find the total value" ** for the set of data.

--For statement The initial value of sum is set to 0 and the values of each element are added. The final value is the total value. ** "How to find the total value" ** is implemented.

The for statement is called ** "instructive" ** compared to the Stream. "Declarative" writing allows you to hide small implementations and reduce the amount of code. It's just a declaration of what to do, so it's easy to understand what you want to do from the code. On the other hand, "instructive" writing is difficult to understand and causes bugs as the processing becomes more complicated because the contents to be done are described in detail.

Experience has shown that less imperative code in a method is easier to read and safer, regardless of whether Stream is used or not.

Performance improvement through parallel processing

There are many cases where this benefit cannot be obtained. (Experience) This is because it is ** faster to run in series ** unless you are dealing with too much data.

Fortunately, all you have to do to turn Strean execution into parallelism is to call parallel (). You can maximize the benefits of parallel processing by considering ** the amount of data ** and ** the problem of not maintaining the order ** instead of parallel processing in the dark clouds.

The following articles will be helpful. Reference: Basic knowledge of functional interface and Stream API that shows its true potential in lambda expressions (3/3)

How do you write

There are ** 3 steps ** to write a process using Stream.

  1. Create a Stream
  2. Intermediate operation
  3. Termination operation

An example is as follows.

stream.java


final int sum = widgets.stream() //Create Stream
                       .filter(w -> w.getColor() == RED) //Intermediate operation
                       .mapToInt(w -> w.getWeight()) //Intermediate operation
                       .sum(); //Termination operation

Create a stream

I think that it is often created from a Collection such as List or Set. That's exactly what widgets.stream () is. In addition, there are methods such as creating a Stream from several values and using Stream.Builder.

Intermediate operation

filter`` mapToInt is the intermediate operation. It converts the value of each element and extracts the element based on the condition. Returns a Stream such as Stream <T> or ʻIntStream. A method chain is possible because it returns a Stream. The processing of the intermediate operation is not the timing when fileter` etc. is called. It is executed for the first time when the termination operation is executed. This is an intermediate operation

Example of actually knowing the execution timing of the intermediate operation
public class Main {
    public static void main(String[] args) throws Exception {
        List<Widget> widgets = List.of(new Widget(RED, 10), new Widget(BLUE, 20));
        
        Stream<Widget> stream1 = widgets.stream();
        Stream<Widget> stream2 = stream1.filter(w -> w.getColor() == RED);
        System.out.println("complete filtering");
        IntStream stream3 = stream2.mapToInt(w -> w.getWeight());
        System.out.println("complete mappint to integer");
        final int sum = stream3.sum();
    }
}

class Widget {
    private Color color;
    private int weight;
    
    public Widget(Color color, int weight) {
        this.color = color;
        this.weight = weight;
    }
    public Color getColor() {
        System.out.println(color);
        return color;
    }
    public int getWeight() {
        System.out.println(weight);
        return weight;
    }
}
complete filtering
complete mappint to integer
java.awt.Color[r=255,g=0,b=0]
10
java.awt.Color[r=0,g=0,b=255]

It can be seen that each process is not executed immediately after the intermediate operation.

Termination operation

sum is the termination operation. Others include collect`` findFirst. Returns the total value or a new collection. Unlike the intermediate operation, the returned value varies. There are many things you can do. "Calculate the total value", "Create a new Collection", "Process for each element (for Each)" and so on.

I think it is better to show an example of what can be done for intermediate operations and termination operations, so I would like to summarize in detail from the next time onwards.

Functional interface

I've ignored it so far, but I would like to mention the existence of w-> w.getColor () == RED`` w-> w.getWeight () . This grammar is called a ** lambda expression **, but you don't necessarily have to write a lambda expression. ** Lambda expressions are just one way to implement a functional interface. ** **

This understanding is unavoidable in understanding Stream, as most methods in Stream take a functional interface as an argument.

To be honest, it's okay to write in an atmosphere at first, so even if you can't understand the worst from here on, it may be okay to write a Stream.

What is a functional interface?

A type of interface. This interface has ** only one method ** What is provided as standard in Java , You can also create your own.

To make each type easy to understand, the first example is divided as much as possible as follows.

Stream<Widget> stream1 = widgets.stream();
        
Predicate<Widget> predicate = w -> w.getColor() == RED;
Stream<Widget> stream2 = stream1.filter(predicate);
        
ToIntFunction<Widget> toIntFunction = w -> w.getWeight();
IntStream stream3 = stream2.mapToInt(toIntFunction);
        
final int sum = stream3.sum();

The Predicate`` ToIntFunction, to which the lambda expression is assigned, is the functional interface. The definition of ToIntFunction is as follows.

java:java.util.function.ToIntFunction.java


@FunctionalInterface
public interface ToIntFunction<T> {
    int applyAsInt​(T value);
}

@FunctionalInterface is added to the functional interface, but there is no functional problem even if it is not added.

Implementation method

You need to create an instance that implements a functional interface to pass to a Stream method, but there are three main ways to implement it.

--Anonymous class --Lambda expression --Method reference

is.

Let's compare each implementation method. As a premise, we assume that you have a Widget class like this.

Widget.java


class Widget {
    private Color color;
    private int weight;
    
    public Widget(Color color, int weight) {
        this.color = color;
        this.weight = weight;
    }
    
    public Color getColor() {
        return color;
    }
    
    public boolean isColorRed() {
        return color == RED;
    }
    
    public int getWeight() {
        return weight;
    }
}

Anonymous class

It's not very easy to read.

Anonymous class.java


final int sum = widgets.stream()
                .filter(new Predicate<Widget>() {
                    public boolean test(Widget w) {
                        return w.isColorRed();
                    }
                }) 
                .mapToInt(new ToIntFunction<Widget>() {
                    public int applyAsInt(Widget w) {
                        return w.getWeight();
                    }
                }) 
                .sum();
You can still do this ...

Of course this still works.

static class WidgetTestColorIsRed implements Predicate<Widget> {
    public boolean test(Widget w) {
        return w.isColorRed();
    }
}
static class WidgetToWeightFunction implements ToIntFunction<Widget> {
    public int applyAsInt(Widget w) {
        return w.getWeight();
    }
}
final int sum = widgets.stream()
                .filter(new WidgetTestColorIsRed())
                .mapToInt(new WidgetToWeightFunction())
                .sum();

Well, I don't think there are many cases where you can just write.

Lambda expression

It is overwhelmingly easier to read.

Lambda expression.java


final int sum = widgets.stream()
                .filter(w -> w.isColorRed()) 
                .mapToInt(w -> w.getWeight())
                .sum(); 

There are several ways to write a lambda expression.

//No arguments
() -> "constant";

//1 argument
n -> n + 1; //Parentheses can be omitted
(n) -> 2 * n;

//2 or more arguments
(a, b) -> Math.sqrt(a * a + b * b);
(x, y, z) -> x * y * z;

//Multiple lines
(a, b, c) -> {
     double s = (a + b + c) / 2;
     return Math.sqrt(s * (s - a) * (s - b) * (s - c));
}

Method reference

I haven't used it so far, but if you can write it with a method reference, it will be easier to see the code if you use it positively **. It's easy to read because you don't have to put a variable like w and you know what the Stream element type is at that point.

Write like class :: method.

Method reference.java


final int sum = widgets.stream()
                .filter(Widget::isColorRed)
                .mapToInt(Widget::getWeight)
                .sum();

Anonymous classes, lambda expressions, and method references are all ways to instantiate a functional interface. You can create an instance in the same way even if it is a functional interface defined by yourself. In functional interfaces, we know that we only need to implement one method, so inference can be done even in writing without any type information, such as lambda expressions.

in conclusion

Next time, I would like to introduce the specific usage of Stream.

reference

-Official Reference -Declarative? Declarative? What do you mean? -Basic knowledge of functional interface and Stream API that shows its true character in lambda expressions (3/3) -What is functional programming, what is lambda -Modern Java Learned with Lambda Expressions and Stream APIs-The Present of the Java Language that Changes by Incorporating Functional Types

Recommended Posts