エンターテイメント!!

遊戯王好きのJavaエンジニアのブログ。バーニングソウルを会得する特訓中。

Javaラムダ式の基礎知識

Javaラムダ式

今更な気がするが、実務で使えなかったからきちんと考えこむことが出来なかった。
今やっと使えるようになったので、まとめる。

ラムダと言われるとラムダ・ドライバを真っ先に思い浮かべる。
ブラックボックス化しているわけではなく、きっちりと読めるし理解も出来る。

必要性

なくても実装は問題ない。
使わなくても実装することはできる。
しかし、チームで開発する場合、書ける人はいるもの。
その際にラムダ式が全く読めないでは、大問題になる。
書けるようになったからには読めるようにしておかないと、Javaエンジニアとして失格。
ラムダ式の知識は必要不可欠!

ラムダ式

Java8で導入された内部クラスを簡易的に書ける記法。
Java8導入された関数型インターフェース*1の実装を簡単にできるようになる。

基本形

引数と処理を->でつなぐだけ ( 引数 ) -> { 処理 }

以下、関数型インタフェースのConsumerの実装例。

Java7以前

Consumer<String> cons = new Consumer<String>() {
    @Override
    public void accept(String str) {
        System.out.println(str);
    }
};

インタフェースなので、当然インスタンスはnewするだけでは出来ない。
抽象メソッドを実装してあげる必要がある。

Java8以降

Consumer<String> cons = ( str ) -> { System.out.println(str) };

異様にシンプル化する。
いろいろなモノが省略されている。
省略できるようになっているのは、自明であるため。
以下理由付きでの説明

  • クラス名:代入先で自明のため
  • 引数の型:実装するメソッドが一つしか無いので、自明のため省略
  • メソッドの宣言:引数と同様、すべて自明のため省略
  • 末尾のセミコロン:1行だけなら不要

最初のうちは、読めない事のほうが多い。
慣れてしまうとラムダ式で書かれた方がしっくり来るし、楽。
まずは、冗長でもいいから記述して、そこからラムダ式を適用する訓練をする。
そうすると次第に覚える。

ラムダ式の変数のスコープ

ラムダ式のキャスト

Java8では型推論が強化された。
型に変換する手がかりが何か1つでもあれば、推論可能!
ラムダ式は、関数型インタフェースである必要があるので、型は推論可能なハズ。
型を特定できる要素があれば、型解決してくれる。
Runnable r = () -> System.out.println("俺のターン!");
Runnableに特定出来るので、コンパイルエラーにならない。

しかし、型が特定出来ないような状態だとコンパイルエラーが発生する。
Object r = () -> System.out.println("俺のターン!");
Object型はすべてのクラスが継承しているので型が特定できない。

この場合は、キャストを噛ませると型が特定できるので、コンパイルエラーが発生しなくなる
Object r = (Runnable) () -> System.out.println("俺のターン!");
実際にこんな使い方をするか疑問であるが、裏の動きをしるために重要

交差型キャスト

「(インターフェース名1 & インターフェース名2 & …)」という形で、キャストするときに「&」でインターフェース名を繋いでいく。
参照型のオブジェクトの指定も可能。
その場合は、つ目の型は参照型で,二つ目以降の型はインターフェースでなければいけない。

参考: JavaSE8リリース記念!マイナーな言語仕様を紹介してみる(交差型キャスト,レシーバパラメータ(仮引数にthis)) - きつねとJava!

マーカーインタフェース(Serializableなど)との併用していくのが正解???

使用例
Supplierに文字出力の実装をしたインスタンスを作り、バイト配列としてByteArrayOutputStream, ObjectOutputStreamを使って出力する。
その後、バイト配列にした時と逆の動きをして、Supplier にキャストして動作した時にどうなるかを実証。

Supplier s = (Supplier<String> & Serializable) () -> "満足させてくれよ";

byte[] byteArray;
try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
    try (ObjectOutputStream oos = new ObjectOutputStream(bos)) {
        oos.writeObject(s);
    }

    byteArray = bos.toByteArray();
} catch (Exception e) {
    throw e;
}

try (ByteArrayInputStream bis = new ByteArrayInputStream(byteArray)) {
    try(ObjectInputStream ois = new ObjectInputStream(bis)) {
        Supplier readObject = (Supplier)ois.readObject();
        System.out.println(readObject.get());
    }
}

結果
満足させてくれよ
満足できました!
つまりは、文字列だけでなく関数も出力可能ってわけですね。
驚愕の事実

*1:定義されている抽象メソッドが1つだけのインターフェース