Java8入門:Lambda式

Java Logo

Java8で追加されたLambda式。Lambda式のおかげで、匿名クラスを簡単に表現できるようになった。

Lambda式の目的

Java8ではStream APIの導入により、内部イテレータの繰り返し処理が

Java SE 7/8 速攻入門の本には、以下のように記載されている。

内部イテレータを使用したデータ処理を行うライブラリとして導入されたStream APIです。Stream APIを使用することで内部イテレータを記述した処理を簡単にパラレル化することができます。

内部イテレータの繰り返し行う処理を関数やクロージャなどで表します。関数などで表すことにより、外部イテレータで記述するよりも処理の独立性を高めることができ、パラレル処理への移行を容易に行うことができます。

Javaでは匿名クラスを使って内部イテレータの処理を表している。しかし、匿名クラスを使うと処理の記述量が増えてしまう。そのため、匿名クラスを簡単に表現できるようにしたものがLambda式とのこと。

Lambda式は、Stream APIとセットで使っていくイメージである。

関数型インターフェース

関数型インターフェースは、実装すべきメソッドが1つのインターフェースのことである(defaultメソッドとstaticメソッドは除く)。標準では、以下のインターフェースがある。

java.util.function (Java Platform SE 8 )

クラス名 説明
Function<T,R> 1つの引数を受け取って、結果を生成する
Consumer 1つの入力引数を受け取って、結果を返さない
Consumer 1つの入力引数を受け取って、booleanを返す
Supplier 入力引数はなく、結果を生成する

上の表は代表的なクラス。他にも大量のクラスあるけど、引数の数と戻り値の有無でパターンが増えているだけである。JavaDocをみると、defaultメソッドとstaticメソッドを除くと、1つしかメソッドが定義されていない。なので、これらは関数型インターフェースである。

Java8ではインターフェースにメソッド実装を持てるようになった。defaultメソッドとstaticメソッドである。過去との互換性を保ちながら機能拡張させるために、このような形となったようだ。実装を持てるのはイマイチだなと思う。積極的に使っていく機能ではない。

自分で関数型インターフェースを作成するときは、@FunctionalInterfaceアノテーションをつけて、メソッドが1つのインターフェースを作れば良い。こんな感じ。

Sample/FunctionalInterfaceSample.java at master · eiKatou/Sample

public static void main(String[] args) {
  MyFunction<Integer, Integer, String> myFunc1
    = (x, y) -> String.format("myFunc1:%s,%s", x.toString(), y.toString());
  System.out.println(myFunc1.exec(101, 102));
}

@FunctionalInterface
interface MyFunction<S, T, R> {
  R exec(S s, T t);
}

Lambda式の書き方

引数が1つのとき、以下のように書くことができる。

x -> 実行処理;

匿名クラスから省略していって、この形になっていく経緯を理解しておく。

Sample/FaunctionSample.java at master · eiKatou/Sample

// 通常の匿名クラスの記述
Function<Integer, String> func1 = new Function<Integer, String>() {
  @Override
  public String apply(Integer t) {
    return "func1:" + t.toString();
  }
};
System.out.println(func1.apply(101));

// Lambda式での記述
Function<Integer, String> func2 = (Integer x) -> {
  return "func2:" + x.toString();
};
System.out.println(func2.apply(102));

// 型推論でIntegerを省略可能
Function<Integer, String> func3 = (x) -> {
  return "func3:" + x.toString();
};
System.out.println(func3.apply(103));

// 引数が1つの時は丸カッコを省略可能
Function<Integer, String> func4 = x -> {
  return "func4:" + x.toString();
};
System.out.println(func4.apply(104));

// 実行文が1つの時は波カッコを省略可能
Function<Integer, String> func5 = x -> "func5:" + (x).toString();
System.out.println(func5.apply(105));

引数がないときは、以下のように書く。

// 引数がないとき
Supplier<String> func6 = () -> "func6:test";
System.out.println(func6.get());

引数が2つ以上あるときは、以下のように書く。

// 引数が2つあるとき
BiFunction<Integer, Integer, String> func7
  = (x, y) -> "func7:" + String.valueOf(x.intValue() + y.intValue());
System.out.println(func7.apply(5, 13));

関数合成

関数合成で2つの関数を合体できる。1つ目の関数の戻り値の型と、2つ目の関数の引数の型が一致していないといけないようだ。

// andThenで合成関数を作る
Function<Integer, Integer> funcPlus = x -> x+100;
Function<Integer, String> funcPrint = x -> "funcPrint:" + (x).toString();
Function<Integer, String> funcPlusPrint = funcPlus.andThen(funcPrint);
System.out.println(funcPlusPrint.apply(500));

// composeで合成関数を作る
System.out.println(funcPrint.compose(funcPlus).apply(600));

funcPlusの戻り値がIntegerなので、funcPrintはIntegerを引数に取っている。

メソッド参照

Lambda式を書かずに、メソッド名だけを記述することができる。staticメソッドの場合は下記の書き方となる。ドットではなくコロンを使用する。

System.out::println

インスタンスメソッドの場合はobject::methodの形になる。コンストラクタもメソッド参照で書くことができる。

Sample/MethodReferenceSample.java at master · eiKatou/Sample

// インスタンスメソッド参照
List<String> list = new ArrayList<>();
Consumer<String> addListFunc1 = list::add;
addListFunc1.accept("aaa");

// コンストラクタ参照
IntFunction<String[]> arrayFunc2 = String[]::new;
String[] array2 = arrayFunc2.apply(13);

参考

Java7/8を勉強するのにおすすめの本。
今回も参考にさせていただいた。

現場で使える[最新]Java SE 7/8 速攻入門
櫻庭 祐一
技術評論社
売り上げランキング: 467,147

以下のスライドも非常に参考になった。

社内Java8勉強会 ラムダ式とストリームAPI from Akihiro Ikezoe
投稿日 2017年10月18日