mokky14's IT diary

IT関係の仕事メモ、勉強会の感想など書いてます。

Java8 lambda式の復習

桜庭祐一さん(@skrb)のlambda式 ハンズオンの復習を兼ねてまとめ。

ハンズオンの教材:
skrb/LambdaDojo · GitHub

lambda式を使うメリット

lambda式を使用する意味は、処理のパラレル化。
処理を複数CPUで並列に処理することで、高速化を図る事が出来る。

Functional Interface

lambda式はfunctionを書くもの。
stream apiiteratorを書くもの。

lambda式は、Functional Interfaceのメソッドをimplementsする。

Interface method
Runnable void run()
Callable T call()
Comparator boolean compare(T t1, T t2)

メソッド名を覚えておく必要はあまりなく、引数と戻り値さえ覚えておけばOK。
他にも、java.util.function packageに色々ある。

Functinal Interfaceを自作する場合は、@FunctionalInterfaceアノテーションを付与すると、コンパイラがFunctionalなInterfaceになっているかをチェックしてくれる。

演習

Functional Interfaceの実装

基本的なlambda式の書き方から。

    Comparator<Integer> comparator1 = new Comparator<Integer>() {
        @Override
        public int compare(Integer x, Integer y) {
        return x - y;
        }
    };

ComparatorはFunctinal Interface。
このComparatorの無名クラスの定義は以下のように書き換えられる。

       Comparator<Integer> comparator1 = (x, y) -> x - y;

なお、引数の型省略は、片方のみ(例えば(x, Integer y)のような)は出来ない。型省略時は両方の引数の型を省略する。
余談として、Intellijでは、特に操作しなくてもラムダ式に書き換えられる。

引数がない場合のlambda式の記述方法。引数指定がない場合でも()は省略不可。

    Callable<Date> callable1 = () -> new Date();

下記のような記載も可。(コンストラクタリファレンス)

    Callable<Date> callable2 = Date::new;

ループ処理

    List<String> strings = Arrays.asList("a", "b", "c", "d", "e");
    
    StringBuilder builder = new StringBuilder();
    for (String s: strings) {
        builder.append(s);
    }

forループは、forEachメソッドでのループに置き換える。

    List<String> strings = Arrays.asList("a", "b", "c", "d", "e");

    StringBuilder builder3 = new StringBuilder();
    strings.forEach(t -> builder3.append(t));

メソッドコールをメソッドリファレンスの形式にすると、引数の記載も不要。

    List<String> strings = Arrays.asList("a", "b", "c", "d", "e");

    StringBuilder builder4 = new StringBuilder();
    strings.forEach(builder4::append);

filterメソッド

    List<Integer> numbers = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    for (Integer x : numbers) {
        if (x % 2 == 0) {
            System.out.print(x);
        }
    }
    System.out.println();

forはforEachに、elseを持たないifはfilterに置き換え。

    numbers.stream().
            filter(x -> x % 2 == 0).
            forEach(System.out::print);
    System.out.println();

数値計算

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

    double ave = 0.0;
    for (Integer x : numbers) {
        ave += x;
    }
    System.out.println(ave / numbers.size());

forEachメソッドを使用して置き換え。
なお、無名クラスと同様に、finalでないローカル変数にはラムダ式からはアクセス出来ない。ave2変数はクラス変数とする必要がある。

    // ave2はクラス変数
    numbers.forEach(x -> ave2 += x);
    System.out.println(ave2 / numbers.size());

数値の合算をreduceメソッドに置き換え。(reduceは値を集約するメソッド。第一引数に初期値、第二引数に値集約を行うラムダ式を指定する)

    int ave3 = numbers.stream()
            .reduce(0, (x, y) -> x + y);
    System.out.println(ave3 / numbers.size());

Integer<->intのオートボクシング回数を削減するため、mapToIntでintに置き換えを行い、Integerへのオートボクシングを抑止。

    int ave4 = numbers.stream()
                .mapToInt(x -> x)
                .reduce(0, (x, y) -> x + y);
    System.out.println(ave4 / numbers.size());

単純な合計値計算は、sumメソッドで出来る。

    int ave5 = numbers.stream()
                .mapToInt(x -> x)
                .sum();
    System.out.println(ave5 / numbers.size());

指定回数分の実行

        // 乱数のリストを作成
        Random random = new Random();

        List<Double> numbers = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            numbers.add(random.nextDouble());
        }

forループをIntStream.range関数に置き換え。range関数は、指定した数値の範囲(回数)だけ処理を行う。

    List<Double> numbers2 = new ArrayList<>();
        IntStream.range(0, 100)
            .forEach(i -> numbers2.add(random.nextDouble()));

予め空リストを作って値を追加しくていくのではなく、collectメソッドでコレクションに変換する。collectメソッドの引数にList変換関数を指定する事で、Listへの変換を行う。

    List<Double> numbers3 =
        IntStream.range(0, 100)
            .mapToObj(i -> random.nextDouble())
            .collect(Collectors.toList());


桜庭さんのIT Proの連載。

Java技術最前線 - Java技術最前線:ITpro