[JAVA] Let's dig a little deeper into the Stream API, which I understand is a rewrite of for.

While writing Java's Stream API, what does this mean? I've summarized a little about what I was wondering about. It's not a collection of Stream API tips, so I'm sorry if you read the article with such expectations.

Summary of this article

You can get a little understanding of this element by reading this article. --Functional interface --Intermediate processing and termination processing

Aim of this article

Learn the grammatical elements of the Stream API that you somehow understand.

What is the way to write Stream API in the first place?

The way to write the Stream API in the case of processing such as displaying only even numbers from 1 to 10 is as follows.

Arrays.stream(new int[]{1,2,3,4,5,6,7,8,9,10})
     .filter( i -> (i % 2) == 0 )
     .forEach( i -> System.out.println(i));

(There is also a cool shape using IntStream.range, but leave it alone ...) By the way, the conventional way to write for and if statements is as follows.

for(int i : new int[]{1,2,3,4,5,6,7,8,10}) {
     if((i % 2) == 0) {
         System.out.println(i);
     }
}

With such a short process, the number of lines is the same and it looks the same. However, if it becomes a complicated process, if you remember the methods such as filter and forEach of Stream API, I personally think that it will be easier to see and understand because it is clear where and what you are doing. But when you look at it suddenly, what is this argument? What is the difference between filter and for Each? Because it becomes I tried to summarize a little.

Functional interface

The argument part seen in the above source is unfamiliar if it is the first time.

filter( i -> (i % 2) == 0 )

Actually, this is a way of writing that takes a functional interface as an argument, and it is abbreviated to a lambda expression. I think that those who are mainly working before java7 will see it for the first time. In java8 or later, those that satisfy the condition of functional interface are lambda expressions that directly set the processing of the function as an argument. You can write it like this.

What is the definition of a functional interface?

Roughly speaking, it is an interface in which only one abstract method is defined. An example is like this.

@FunctionalInterface
public interface Predicate<T> {
     boolean test(T t);
}

As you can see, it's just an interface. It is important that there is only one abstract method, and you can write it as a lambda expression that describes only the processing in this form. (It doesn't matter if the default method is written.) By the way, Functional Interface only states that it can be used in functional interfaces. It is not processally meaningful.

Set a functional interface as an argument with a lambda expression

If you refer to the filter method, filter receives the Predicate interface as an argument.

  Stream<T> filter(Predicate<? super T> predicate);

In the above source code, it was written like this.

filter( i -> (i % 2) == 0 )

The Predicate abstract method is the test method. So, just specify the process of receiving one argument and returning boolean as a return value in the lambda expression. (Actually the above example is that.) However, even if it is said that the above writing method is a lambda expression, what is a lambda expression? ?? ?? It will be Let's take a quick look at the grammar.

Lambda expression

The basic grammar of a lambda expression looks like this.

(argument) -> {processing}

It's like setting an argument on the left side of the arrow and writing the process on the right side. In other words, if you look at the filter lambda expression I wrote this time without omitting it, it looks like this.

filter( (int  i) -> {return (i % 2) == 0;} )

Even if you are new to this writing style, you may be able to make a judgment. In the sample at the top, I just omitted everything that can omit the argument type and so on. (I think the rule of omitting lambda expressions will come up again in the article.)

Simply put, the argument types and () are omitted, and the return statement and {} are omitted. All are conditional, but if you omit them, the outlook will improve. You can understand it by studying grammar, so I think it's a good idea to omit the parts that can be omitted.

Types of functional interfaces

Various functional interfaces are defined as standard. (By the way, Effective Java does not recommend creating your own functional interface, so When using it, try to use the standard one. ) However, it can be roughly divided into four systems. After that, it is dedicated to primitive things such as int and long, and it has two arguments. It's a good idea to remember the basics and look them up on a case-by-case basis. (Because there are so many, I couldn't do it in my head ...)

For now, let's remember the existence and methods of the basic Supplier, Consumer, Predicate, and Function.

//Supplier interface
//It defines a get method that does not receive an argument and returns a value.
@FunctionalInterface
public interface Supplier<T> {
    T get();
}

//Consumer interface
//Defines an accept method that takes an argument and does not return a return value.
@FunctionalInterface
public interface Consumer<T> {
     void accept(T t);
}

//Predicate interface
//It defines a test method that takes an argument and returns a boolean.
@FunctionalInterface
public interface Predicate<T> {
     boolean test(T t);
};

//Function interface
//It defines an apply method that takes an argument of any type and returns a return value of any type.
@FunctionalInterface
public interface Function<T, R> {
     R apply(T t);
}

By the way, if you want to see all kinds, it is recommended to check the "java.util.function" package.

Summary of functional interfaces

--The functional interface is lambda-style and looks smart. ――It is important to remember the standard functional interface.

Intermediate processing and termination processing

The Stream API processing flow is mainly divided into three flows.

  1. Stream generation This time too, the stream is generated by Arrays.stream (). It just creates a stream, so I won't go into detail.
  2. Intermediate processing In this case, filter is applicable. Intermediate processing can be done many times in one Stream.
  3. Termination In this case, for Each is applicable. Termination can only be called once in a Stream.

The processing flow of Stream API is generation → intermediate processing → termination processing. But why can the intermediate process be executed multiple times and the termination method only once? This much information from common explanations can be confusing.

Separate intermediate processing and termination processing

To be honest, it is difficult to remember and distinguish only the intermediate processing and termination processing methods. (How many should I remember ...) It is more important to understand the flow of intermediate and termination processing than to remember the method.

It appears many times, but the same source code again.

Arrays.stream(new int[]{1,2,3,4,5,6,7,8,9,10})
     .filter( i -> (i % 2) == 0 )
     .forEach( i -> System.out.println(i));

The important thing here is that you can call the forEach method after the filter method. By the way, it doesn't work even if I write it like this.

//Bad example(Or rather, a compile error)
Arrays.stream(new int[]{1,2,3,4,5,6,7,8,9,10})
     .forEach( i -> System.out.println(i));
     .filter( i -> (i % 2) == 0 )

In fact, the reason why you can call the forEach method after the filter method can be understood directly by looking at the Stream interface.

Intermediate processing and termination processing understood by Stream interface

The source code below is an abstract method of the Stream interface.

public interface Stream<T> extends BaseStream<T, Stream<T>> {
     //Return value is Stream
     Stream<T> filter(Predicate<? super T> predicate);
     <R> Stream<R> map(Function<? super T, ? extends R> mapper);
     IntStream mapToInt(ToIntFunction<? super T> mapper);
     LongStream mapToLong(ToLongFunction<? super T> mapper);
     DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);
     <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>>
mapper);
     IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper);
     LongStream flatMapToLong(Function<? super T, ? extends LongStream>
mapper);
     DoubleStream flatMapToDouble(Function<? super T, ? extends DoubleStream>
mapper);
     Stream<T> distinct();
     Stream<T> sorted();
     Stream<T> sorted(Comparator<? super T> comparator);
     Stream<T> peek(Consumer<? super T> consumer);
     Stream<T> limit(long maxSize);
     Stream<T> substream(long startInclusive);
     Stream<T> substream(long startInclusive, long endExclusive);

     //Various return values
     void forEach(Consumer<? super T> action);
     void forEachOrdered(Consumer<? super T> action);
     Object[] toArray();
     <A> A[] toArray(IntFunction<A[]> generator);
     T reduce(T identity, BinaryOperator<T> accumulator);
     Optional<T> reduce(BinaryOperator<T> accumulator);
     <U> U reduce(U identity,
                  BiFunction<U, ? super T, U> accumulator,
                  BinaryOperator<U> combiner);
     <R> R collect(Supplier<R> resultFactory,
                   BiConsumer<R, ? super T> accumulator,
                   BiConsumer<R, R> combiner);
     <R> R collect(Collector<? super T, R> collector);
     Optional<T> min(Comparator<? super T> comparator);
     Optional<T> max(Comparator<? super T> comparator);
     long count();
     boolean anyMatch(Predicate<? super T> predicate);
     boolean allMatch(Predicate<? super T> predicate);
     boolean noneMatch(Predicate<? super T> predicate);
     Optional<T> findFirst();
     Optional<T> findAny();
}

If you look closely, the filter method uses Stream as the return value. And forEach has no return value, right?

This! (Which one)

This is an interface, but if you look at the implementation class "Reference Pipeline" Intermediate methods such as the filter method create a new Stream in the method and set it as the return value. Since Stream is set as the return value, the Stream method can be called continuously. On the contrary, as you can see from the return value of the method, the termination method such as forEach does not set Stream as the return value, so You can't call the Stream method in succession.

If you understand this, intermediate methods and termination methods are very easy. The method that returns Stream is the intermediate process It can be roughly understood as termination processing that Stream is not used as a return value.

Summary of intermediate processing and termination processing

--Since the intermediate process uses Stream as the return value, it can be called many times. --Since the termination process uses a return value other than Stream, it can be called only once.

Summary

--If you understand the functional interface and learn the lambda grammar, the arguments are not scary. --Intermediate processing and terminating processing are big names, but the return value is different.

If you understand, Stream API is not scary.

TODO --I heard that you can set arguments with method references. ――I want various examples.

Recommended Posts

Let's dig a little deeper into the Stream API, which I understand is a rewrite of for.
[Swift, a must-see for fledgling! ] Let's understand the implementation of communication processing of WebAPI
Java: The problem of which is faster, stream or loop
Let's consider the meaning of "stream" and "collect" in Java's Stream API.
[java8] To understand the Stream API
A story that I wanted to write a process equivalent to a while statement with the Stream API of Java8
I tried to summarize the Stream API
Kanban is not the only breakthrough, let's use Microsoft Project a little more