2016/04/25更新

[Java] Java8のラムダに超入門(書き方、関数型インターフェース、独自に定義、ラムダ受け取り処理)

このエントリーをはてなブックマークに追加      

こんにちは、@yoheiMuneです。
仕事でJava8を使おうと思い勉強中です。今日はJava8のラムダ機能について、いろいろと書いてみたいと思います。いろいろな記事でラムダが言及されてはいたものの、少し断片的だったんですよね。この記事はいい感じにまとめられたのではと思います。

画像

目次




Java8のラムダ式とは

ラムダ式はひとまとまりの処理を記述できる新しい仕様です。例えば、Java7まではソート処理を以下のように書きますが、
// 無名クラスを使った場合(Java7まで)
Collections.sort(userList, new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
        return o1.compareToIgnoreCase(o2);
    }
});
Java8のラムダを使うと、以下のように簡単に記述することができます。
// ラムダを使った場合(Java8)
Collections.sort(userList, (o1, o2) -> o1.compareToIgnoreCase(o2));
上記の例はCollections.sortメソッドでラムダを使う例ですが、他の多くのメソッドでもラムダを使うことができるようになりました。

それではここから、ラムダを詳しく見ていきたいと思います。



ラムダの書き方

ラムダ式は今までのJavaには無かった記法です。以下のルールで記述することができます。

基本形

// 処理が複数行の場合には、{}をつける
// 引数には型を明示することができる
(int x, int y) -> {
    return x + y;
};

引数の型定義は省略できる

// 引数には型定義は省略できる
(x, y) -> {
    return x + y;
};

処理を1行で書くなら、カッコもreturnも要らない

// 処理ブロックが1行の場合には、{}もreturnも不要です。
(x, y) -> return x + y;

引数が1つなら、引数のカッコも省略できる

x -> return x * 2;

引数なしの場合は、()で表現する

() -> return 1;
ラムダ式を1行で書けると、非常にシンプルにかけて素敵ですね!



関数型インターフェース

Java8から関数型インターフェースという考え方が導入されました。ラムダ式は関数型インターフェースを実装した無名クラスのインスタンス?として扱われます。

関数型インターフェースの条件

関数型インターフェースは、「抽象メソッドが1つ」だけ持つインターフェースです。例えば以下のインターフェースは、関数型インターフェースです。
interface Something {
    int execute(String data);
}
また、関数型インターフェースを明示するために@FunctionalInterfaceアノテーションを付与することができます。
@FunctionalInterface
interface Something {
    int execute(String data);
}
このアノテーションを付与することで、付与対象のインターフェースが関数型インターエースの条件をみたなさい場合にコンパイルエラーを出すことができます。


java.util.functionパッケージ

Java8では、よく使われそうな関数型インターフェースがいくつか提供されています。それらはjava.util.functionパッケージで提供されていて、以下のような種類が存在します。
// Supplier系
// 引数なしで値を返す
Supplier lambda1 = () -> "aaa";

// Consumer系
// 値を受け取るが、返却しない(処理を行う)
Consumer lambda2 = (str) -> System.out.println(str);

// Predicate系
// 値を受け取って判定を返す
Predicate lambda3 = (str) -> str == "hello";

// Function系
// 値を受け取って、何か値を返す
Function lambda4 = (str) -> str.length();

// 上記それぞれで、
// 引数を2つ受け取る場合には「BiXXX」を使う
BiFunction lambda5 = (str, num) -> str + num;

// 上記それぞれで、
// プリミティブ型に特化したものが存在する(int, long, double)
IntPredicate lambda6 = (num) -> num > 10;
詳細は、Java関数型インターフェースメモ(Hishidama's Java8 Functional Interface Memo)がわかりやすくまとめられています。


独自の関数型インターフェースとラムダを定義する

ここではお試しで、引数を3つ受け取る独自の関数型インターフェースを定義して、それをラムダで表してみましょう。
// 関数型インターフェースを定義
@FunctionalInterface
interface TriFunction<T, S, U, R> {
    R apply(T condition, S o1, U o2);
}
// ラムダ式で表す
TriFunction<Boolean, String, String, String> lambda01
    = (condition, o1, o2) -> condition ? o1 : o2;
このように独自の関数型インターフェースを定義して、自由自在にラムダ式の型を定義することができます(ただ実際には、ほとんどのユースケースでjava.util.functionパッケージの既存インターフェースでもの足りそうです)。



ラムダ式を受け取る処理を実装する

ここではラムダ式を受け取るメソッドの実装方法を書きたいと思います。ラムダ式は上述の通り、なんらかの関数型インターフェースを実装したものです。メソッドで受け取る場合には以下のように行います。
// 引数を関数型インターフェース型で受け取る
String process(Function<String, String> convertor) {
    // 後で
}
そして、このメソッドの中でラムダ式を実行する場合には、以下のように行います。
String process(Function<String, String> convertor) {
    // 関数型インターフェースのメソッド(ここではapply)を呼び出して、ラムダ式を実行する
    String result = convertor.apply(this.name);
    return null;
}
Functionインターフェースの場合にはapplyメソッドを呼ぶことで処理を実行することができます。呼び出すメソッドは関数型インターフェースごとに違うので、利用する関数型インターフェースの抽象メソッド名を確認します。



参考資料

ラムダ式を学ぶために以下の記事を読みました。ありがとうございます。

関数型プログラミングって何、ラムダってなんだよ - Qiita

Java関数型インターフェースメモ(Hishidama's Java8 Functional Interface Memo)

5分で読む入門編:Java 8 ラムダ式 TECHSCORE BLOG

ゆるふわJava8入門 | Slideshare

Java8の新機能に関するメモ - Qiita

Java 8 新機能つまみぐい - Qiita

Java新機能メモ(Hishidama's Java up-to-date)



最後に

今回の記事を書くのにたぶん5日ほどかかりました。書いてはみたもののなんだかしっくりこなくて、色々と調べなおしたり順番を変えたりしながらなんとか書いた次第です。ラムダを書くだけならなんでもないのですが、どのような仕組みで成り立っているのか、どのように応用/活用していけばいいのか、などを調べて試しているとかなり時間がかかってしまいました。けど、最終的には良いブログが書けたのではと思っています。

最後になりますが本ブログでは、Java・フロントエンド・Python・機械学習など雑多に情報発信をしていきます。自分の第2の脳にすべく、情報をブログに貯めています。気になった方は、本ブログのRSSTwitterをフォローして頂けると幸いです ^ ^。

最後までご覧頂きましてありがとうございました!





こんな記事もいかがですか?

RSS画像

もしご興味をお持ち頂けましたら、ぜひRSSへの登録をお願い致します。