java8でLambda書いた時に色々と調べた事の自分用メモ兼ねてます
基本的な書き方
( 引数 ) -> { 処理 }
引数と処理を->
でつなぐだけなので、簡単にLambdaを書くことが出来る。
具体的に色々な例を下記に記載していきます。
基本形
java8では、ほとんどのケースでfor文を使用しなくてもよいので、まずはそれを書き直す基本的なやり方です。
ListでLambda
List<String> stringList = new ArrayList<String>();
stringList.add("japan");
stringList.add("usa");
stringList.add("england");
for (String string : stringList) {
System.out.println(string);
}
List<String> stringList = new ArrayList<String>();
stringList.add("japan");
stringList.add("usa");
stringList.add("england");
stringList.forEach(string -> System.out.println(string));
Lambdaでは、引数や処理の省略をすることが可能なので、出来る限り省略する形で書きました。
省略ルールは以下な感じです。
- 戻り値がvoidだとreturnは省略可能
- 処理が1行のみの場合
{}
で囲わなくてよく、文末の;
も省略可能 - 引数1つのみの場合は
()
を省略可能 - 引数の型は省略可能(全て省略するか、全て書くかの2択)
他にも省略可能なルールが存在しますが、良くわかってないので書いてません。
また、メソッド参照を使用して記述することも出来ます。
List<String> stringList = new ArrayList<String>();
stringList.add("japan");
stringList.add("usa");
stringList.add("england");
stringList.forEach(System.out::println);
この記法を使用出来るルールはありますが、Lambdaと合わせることで簡潔で分かりやすい記述を行うことが出来るようになります。
やりすぎると分からなくなるので、Ojbects::nonnullとかXXXX::getIdなど明示的な処理から使い始めてかげんを知るべきかなと思います。
配列でLambda
配列で同様のことを行いたい場合ですが、配列ではforeachが使えないため、Listに変換してから行う必要があります。
String[] stringArray = { "japan", "usa", "england" };
Arrays.asList(stringArray).forEach(string -> System.out.println(string));
追記:
配列でLambdaをやる場合は、Arrays.stream()
メソッドを使うことでも出来ます。
MapでLambda
MapについてはそのままLambdaが適応可能です。
Map<String, String> stringMap = new HashMap<String, String>();
stringMap.put("japan", "japanese");
stringMap.put("usa", "english");
stringMap.put("england", "english");
for (Entry<String, String> entry : stringMap.entrySet()) {
System.out.println(entry.getKey());
System.out.println(entry.getValue());
}
Map<String, String> stringMap = new HashMap<String, String>();
stringMap.put("japan", "japanese");
stringMap.put("usa", "english");
stringMap.put("england", "english");
stringMap.forEach((key, value) -> {
System.out.println(key);
System.out.println(value);
});
細かいLambda仕様
知っておいた方がよいLambda仕様を過剰書きにします
- 変数のスコープは関数内共通なので、変数名はユニークにしないといけない
- Lambda外のローカル変数は書き換え不可
- ListのaddやMapのputは外のローカル変数に定義しているものでも可能
- Exception発生しても上位にthrow出来ないので、try-catchで抑制するか死ぬのを見守るしかない
応用系
JavaのLambdaを書く上でList、配列、Mapの操作を良く使うのですが、上の例だけは実用に耐えません。
Lambdaを実用的に使うには、StreamAPIを使う事となると思っていますので、その説明です。
Streamの基本
Streamを使う際は
- 型をStreamに変換する
- 中間処理を実施する
- 末端処理を実施して戻り値を取得する
というような順番で操作します。
中間処理は抽出(filter)したり、変換したり(map)とか実際にそのStreamにさせたい処理がある程度書かれています。
末端処理はStreamの処理を最終的な結果として戻してもらうための処理です。
ここの説明はかなり適当なので、別の説明の方が分かりやすいと思います(丸投げ)
Streamの変換方法
List<String> stringList = new ArrayList<String>();
stringList.add("japan");
stringList.add("usa");
stringList.add("england");
Stream<String> stream = stringList.stream();
String[] stringArray = { "japan", "usa", "england" };
Stream<String> stream = Arrays.stream(stringArray);
Map<String, String> stringMap = new HashMap<String, String>();
stringMap.put("japan", "japanese");
stringMap.put("usa", "english");
stringMap.put("england", "english");
Stream<Entry<String, String>> stream = stringMap.entrySet().stream();
他にもStreamに変換可能な型は存在していますが、今のところは使ってないので書いてません。
Listを使って幾つかのケースで実際のソースを書いていきます。
Listからの抽出
StringのListから!null and !emptyの文字列を抽出して、その結果をListで戻すという例です
List<String> stringList = new ArrayList<String>();
stringList.add("japan");
stringList.add("usa");
stringList.add("england");
stringList.add("");
stringList.add(null);
System.out.println(stringList);
List<String> filterList = stringList.stream().filter(string -> string != null && !string.isEmpty()).collect(Collectors.toList());
System.out.println(filterList);
Streamでやっている事は、中間処理としてfilterを使って!null and !emptyのStringを抽出しています(抽出なので、true返却で残る)。
抽出後、末端処理としてcollectで結果を作成して、結果としてListを戻すというようなことをしています。
※ 細かい関数仕様はJavaDoc見るなりしてください。
Listのソート
StringのListをソートするやり方です
List<String> stringList = new ArrayList<String>();
stringList.add("japan");
stringList.add("usa");
stringList.add("england");
System.out.println(stringList);
List<String> filterList = stringList.stream().sorted().collect(Collectors.toList());
System.out.println(filterList);
ソートのやり方をお任せするならsortedを中間処理として呼び出せば大丈夫です。
実装側でソートのやり方を指定も出来ます。
List<String> stringList = new ArrayList<String>();
stringList.add("japan");
stringList.add("usa");
stringList.add("england");
System.out.println(stringList);
List<String> filterList = stringList.stream().sorted((p, o) -> p.compareTo(o)).collect(Collectors.toList());
System.out.println(filterList);
sortedはラムダでcompareToと同じ形でラムダを書けばよいので、それで指定可能です。
List<String> stringList = new ArrayList<String>();
stringList.add("japan");
stringList.add("usa");
stringList.add("england");
System.out.println(stringList);
Comparator<String> comparator = (p, o) -> p.compareTo(o);
List<String> filterList = stringList.stream().sorted(comparator).collect(Collectors.toList());
System.out.println(filterList);
sortedに与える引数の正体はComparator<T>
型で、この型はchainさせてのソートも可能となります。
これで複雑なソートも簡単に実装できるようになりました(私が実装したちょっと面倒くさいソートはJava7では解決案が見つからずapache common使って実装してました)
List<String> stringList = new ArrayList<String>();
stringList.add("japan");
stringList.add("usa");
stringList.add("england");
System.out.println(stringList);
Comparator<String> comparator = (p, o) -> p.compareTo(o);
comparator = comparator.thenComparing((p, o) -> o.compareTo(p));
List<String> filterList = stringList.stream().sorted(comparator).collect(Collectors.toList());
System.out.println(filterList);
ちなみにvoid java.util.List.sort(Comparator<T> c)
の関数が新しく定義されているので、stringList.sort(comparator)
とかstringList.sort((p, o) -> p.compareTo(o))
でもソートしてくれます。
List内部の計算
Listの値を全部加算したいとかいう場合に使います。
List<Long> longList = new ArrayList<Long>();
longList.add(1L);
longList.add(2L);
longList.add(3L);
System.out.println(longList);
Long count = longList.stream().reduce(0L, (base, value) -> base + value);
System.out.println(count);
末端処理であるreduceは値の集約のためにあります。
第一引数が初期値、第二引数が集約するための処理です。
第二引数でのラムダ式でさらに2つの引数が渡されますが
- 現在の値
- リストの値
というような意味です。
つまり、ラムダ式で第一引数と第二引数を足していけばListの合計値が得られるという訳です。
もちろん、処理部分には複雑な計算や別関数で計算させたりも出来ます。
List<Long> longList = new ArrayList<Long>();
longList.add(1L);
longList.add(2L);
longList.add(3L);
System.out.println(longList);
Long count = longList.stream().reduce((base, value) -> base + value).get();
System.out.println(count);
reduceでは初期値を与えなくても動きます。
その場合のbaseが最初に実行された場合は、Listの要素0番目にあるデータが入ってきます。
そして、初期値がない場合はOptional<T>
が結果の型となりますので、get関数を呼ばないといけないことに気を付けなければいけません。
と書いた後に単純に末端処理として合計値が得られるsumの存在を知りました。
単純に合計したいだけならsum、別の計算もしたい場合ならreduceと使い分けが必要でした。(sumはプリミティブ型のStreamでしか使えないのですが、まだ分かってないので説明を省きます)
Listの変換
Listを別の型のListに変換したいケースを扱います。
List<String> stringList = new ArrayList<String>();
stringList.add("1");
stringList.add("2");
stringList.add("3");
System.out.println(stringList);
List<Long> longList = stringList.stream().map(string -> Long.valueOf(string)).collect(Collectors.toList());
System.out.println(longList);
mapは新しい型の値を展開するために存在しています。
この関数を使って、別の型を返却してあげるとListの変換が可能となります。
同じ型で値をどうこうしたい場合も、使うことが出来ます。
mapToObj mapToInt mapToLong mapToDouble
といった戻り値の型を限定するmapの関数もあるので、用途に合わせて使うと分かりやすくなります。
Listのマッチ
List内に特定の値があるかのチェックを行います。
List<String> stringList = new ArrayList<String>();
stringList.add("1");
stringList.add("2");
stringList.add("3");
System.out.println(stringList);
boolean result = stringList.stream().anyMatch(string -> "1".equals(string));
System.out.println(result);
anyMatchでList内に特定の要素があるかを末端処理で確認することが出来ます。一度でもTrueが返却されると、最終的にTrueが戻ります。一度もTrueが返却されない場合はFalseが返却されます。
マッチ具合によってallMatch noneMatch
を使い分ければ、List内要素の検証としてどのパターンでも対応することが出来るようになります。
# 最後に
まだまだLamdbaを使ったパターンはありますが、今のところ使ったのは書いたパターンだけです。
ただ、このパターンを使っていくうちに、処理順番に気を付ければ基本的な機能については問題ないなという印象を持ちました。
並列処理やより複雑なケースはまだ実装していないので、ここらへんが来た時はまた別の考えになるかもしれませんが、今のところはってことです。