-[Java11] Stream Summary -Advantages of Stream- -[Java11] Stream Usage Summary -Basics-
Map to collection-A class that supports functional operations on a stream of elements, such as reduce transforms. Source: Official Document
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();
}
}
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.
I think there are two major merits:
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.
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)
There are ** 3 steps ** to write a process using Stream.
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
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.
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
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.
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.
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.
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.
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;
}
}
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();
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.
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));
}
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.
Next time, I would like to introduce the specific usage of Stream.
-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