ゆーいちろーブログ

コンピュータに関することや思ったことをふわふわ書きます.

Java8の関数インターフェースの(de)Serializationで発生しがちなトラブル

こんにちは,ゆーいちろーです. 初記事になります.

Java8から,関数インターフェース,ラムダ式が導入されました. Serializationまで行え,非常に便利で実装の幅が広がりますが, 気をつけておかないと問題になることがあります. 僕はこれでクリスマスを溶かしました(笑)

関数インターフェースはあくまでもインターフェースである

Java8でのラムダ式は,あくまでも関数インターフェースを実装する匿名クラスを定義することとなります. インターフェースは実体化できません(当然ですが私は気づいていませんでした)ので,Jackson等でのJSONと関数インターフェースの相互変換には失敗します. そこで,Serializeを行いたければ別の手法を用いる必要があります.

ラムダ式の交差型キャストによるSerializableの実装

交差型キャストにより,関数インターフェースにSerializableを簡単に実装できるようになります. ラムダ式の場合,(Function<Foo, Bar> & Serializable)のような型にキャストを行うことが必要になりますが, これはラムダ式を関数インターフェースとして定義する瞬間に行う必要があります.

// ダメな例
// java.lang.ClassCastException: test$$Lambda$1/321001045 cannot be cast to java.io.Serializable
Function<Integer, String> f1 = (var) -> var.toString();
Function<Integer, String> f2 = (Function<Integer, String> & Serializable) f1;

// OK
Function<Integer, String> g = (Function<Integer, String> & Serializable) (var) -> var.toString();

理由としては,ラムダ式では変数のキャプチャを行うことができるため, 定義したスコープでしか全ての変数を正しくキャプチャできることは保証されないためではないかと思います. これが許されてしまうと,定義したスコープ外でも,Serializableに変換できることとなってしまいます. 加えて,ラムダ式は匿名クラスを定義することであり,このときにSerializableを実装していないと, いくら親で実装したとしても全体としてSerialize不可になります.

(詳しく理由が分かる方,教えてくださると嬉しいです)

インスタンスメソッドはSerialize不可

関数インターフェースへの代入として,以下の構文が許されます.

import java.io.*;
import java.util.function.*;

public class test
{
        public static void main(String[] args)
        {
                Function<Integer, String> f = test::toStr;
                System.out.println( f.apply(100) ); // 100
        }

        static String toStr( Integer i )
        {
                return i.toString();
        }
}

これは正しく実行されます. また,代入時にキャストを行うことでserialize,deserializeに成功します. toStrがnon-staticなメソッドの場合は,うまくいきません. これは当然ですが,気をつけていないとハマると思います.