Nowadays Java lambda expressions and Stream API

It's been a long time since the release of Java8, but I've been using lambda expressions and Stream API somehow so far, so I'll summarize it again.

Lambda expression

Lambda expressions are an easy way to implement an interface that has only one method, called a functional interface.

Functional interface

In functional languages like lisp and languages with function pointers like C, you can easily pass a "function", but in java you can't pass it unless the function is an object. Therefore, by preparing a functional interface as shown below, it is possible to pass a function.

Func.java


package jp.foo;

public interface Func {
public double apply(double x);
}


A functional interface is an interface that has only one abstract method.

Implementation of functional interface

Until java7, the abstract method was overridden as follows.

Func g = new Func(){
@Override
public double apply(double x) {
return x * x;
}
};



In java8, the description can be simplified as follows by taking advantage of the fact that the functional interface has only one method. This is a lambda expression.

Func f = x -> x * x;


If you write a little more tightly

Func f = (x) -> {return x * x;};


Generally, it is described as (argument 1, argument 2, ...)-> {process 1; process 2; ...}. Of course, if there is no argument or no return, you can write it empty like ()-> {}. This is just overriding a single method in the functional interface. The functional interface simplifies the description by taking advantage of the fact that there is only one method to override.

This lambda expression is useful, for example, when using a method that takes a functional interface as an argument (for example, as shown below).

FuncUser.java


package jp.foo;

public class FuncUser {
public static void samplePrintFunc(Func f) {
for(double x = 1.0; x <= 3.0; x += 1.0) {
System.out.println(f.apply(x));
}
}
}


The simple description will be as follows.

FuncUser.samplePrintFunc(x -> x * x);

FuncUser.samplePrintFunc(x -> Math.sin(x));


If the process becomes a little complicated, you can describe multiple processes by enclosing them in {// describe multiple processes}.

FuncUser.samplePrintFunc(x -> { a = Math.sin(x); b = Math.cos(x); return a * b; });


If there is only one process and the argument type of the functional interface and the argument type of the method you want to use are the same, you can omit it as follows.

FuncUser.samplePrintFunc(Math::sin);


Lambda expressions are thus a powerful grammar for passing functions.

Stream API

The Stream API is a tool that performs collection processing using lambda expressions as described above. It's an API designed with a lambda expression. I've summarized the ones I use often as an example.

forEach It seems that only forEach can be used without calling the stream method.

Arrays.asList(new Double[] { 1.0, 2.1, 3.2, 4.3, 5.4 }).forEach(System.out::println);


filter It is convenient because it performs a refined search. It's easy because you don't have to write an if statement in each for statement.

Arrays.asList(new Double[] { 1.0, 2.1, 3.2, 4.3, 5.4 }).stream().filter(x -> x > 3.0).forEach(System.out::println);


map It is used when you want to create another list and set from list or set.

Arrays.asList(new Double[] { 1.0, 2.1, 3.2, 4.3, 5.4 }).stream().map(x -> x * x).forEach(System.out::println);


collect collect is powerful and almost eliminates writing for statements.

Write collect (supplier (variable placed outside for), accumulator [process inside for], combiner [process to collect supplier at the time of parallel processing]).

Arrays.asList(new Double[] { 1.0, 2.1, 3.2, 4.3, 5.4 }).stream().collect(HashMap::new, (map, x) -> map.put(x, x * x), (map1, map2) -> map1.forEach(map2::put)).entrySet().forEach(System.out::println);


Also, it is convenient to use Collectors. The processing that you often do is well abstracted and complete.

Arrays.asList(new Double[] { 1.0, 2.1, 3.2, 1.0, 5.4 }).stream().collect(Collectors.toSet()).forEach(System.out::println);


mapToDouble etc... If you use the mapTo [numerical value] stream, you can use average, max, and min, which is useful.

Arrays.asList(new Double[] { 1.0, 2.1, 3.2, 4.3, 5.4 }).stream().mapToDouble(x -> x).average().ifPresent(System.out::println)


Try to find the standard deviation

Let's find the standard deviation.

\sigma = \sqrt{\frac{1}{n} \sum_{i = 1}^n (x_i - \mu)^2}


The following desired part itself is multiplied by 2 lines (2nd and 3rd lines).

List<Double> sample = Arrays.asList(new Double[] { 1.0, 2.1, 3.2, 4.3, 5.4 });
double mu = sample.stream().mapToDouble(x -> x).average().getAsDouble();
double siguma = Math.sqrt(sample.stream().map(x -> Math.pow(x - mu, 2.0)).mapToDouble(x -> x).average().getAsDouble());
System.out.println(siguma);