rakugakibox.net

技術ノート。兼JS/CSS実験場。 ♡:Java, Spring, AWS.

Java: 交差型キャストでラムダ式をSerializable, Cloneableにして遊んだ!

ラムダ式の交差型キャストというものを初めて知りました。

キャストだけでインターフェイスを実装したことになるのが面白くて、
ラムダ式を Serializable や Cloneable にして軽く遊んでみました。

経緯

交差型キャスト

交差型キャストについては、
@bitter_fox さんのスライドと記事がとても分かりやすかったです。

環境

$ java -version
java version "1.8.0_65"
Java(TM) SE Runtime Environment (build 1.8.0_65-b17)
Java HotSpot(TM) 64-Bit Server VM (build 25.65-b01, mixed mode)

※以降、どこまでが仕様でどこからが環境依存なのかは分からずに書いてます(^^;

Serializable にしてみる

シリアライズ/デシリアライズして、実行まで出来ちゃいました。

public static void main(String[] args) throws Exception {
    Runnable lambda = (Runnable & Serializable) () -> System.out.println("hoge");

    byte[] bytes = serialize(lambda);
    Runnable lambda2 = deserialize(bytes);

    System.out.println(lambda);     // Hoge$$Lambda$1/868693306@1fb3ebeb
    lambda.run();                   // hoge
    System.out.println(lambda2);    // Hoge$$Lambda$2/2003749087@4eec7777
    lambda2.run();                  // hoge
}

static byte[] serialize(Object object) throws Exception {
    try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos)) {
        oos.writeObject(object);
        return baos.toByteArray();
    }
}

static <T> T deserialize(byte[] bytes) throws Exception {
    try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
            ObjectInputStream ois = new ObjectInputStream(bais)) {
        return (T) ois.readObject();
    }
}

ラムダ式内で外部の変数を使ったらどうなっちゃうんだろー?
外部変数の値ごとシリアライズしちゃってるのかなー?

まずは Serializable でない変数を含むラムダ式にして確認したところ、
writeObjectNotSerializableException となりました。

Object hoge = new Object();
Runnable lambda = (Runnable & Serializable) () -> System.out.println(hoge);

今度は Serializable な外部変数を含むラムダ式にして、
変数の内容をシリアライズ前後で書き換えてみました。

public static void main(String[] args) throws Exception {
    Map<String, String> hoge = new HashMap<>();
    hoge.put("a", "x");
    Runnable lambda = (Runnable & Serializable) () -> System.out.println(hoge);

    hoge.put("b", "y");

    byte[] bytes = serialize(lambda);
    Runnable lambda2 = deserialize(bytes);

    hoge.put("c", "z");

    System.out.println(lambda);     // Hoge$$Lambda$1/868693306@1c20c684
    lambda.run();                   // {a=x, b=y, c=z}
    System.out.println(lambda2);    // Hoge$$Lambda$2/1149319664@7cca494b
    lambda2.run();                  // {a=x, b=y}
}

やはり、外部変数の値ごとシリアライズしてるような動作となりました。
なるほどー。

Cloneable にしてみる

Cloneable にも出来ちゃいました。

public static void main(String[] args) throws Exception {
    Runnable lambda = (Runnable & Cloneable) () -> System.out.println("hoge");
    System.out.println(lambda instanceof Cloneable);    // true
}

でも Object#clone() は protected なのでそのままじゃ呼び出せません。
ならば無理やり呼んでやる!w
とリフレクションしてみたら、普通にcloneできました。

public static void main(String[] args) throws Exception {
    Runnable lambda = (Runnable & Cloneable) () -> System.out.println("hoge");
    Runnable lambda2 = cloneForce(lambda);
    System.out.println(lambda);     // Hoge$$Lambda$1/455659002@4c873330
    lambda.run();                   // hoge
    System.out.println(lambda2);    // Hoge$$Lambda$1/455659002@119d7047
    lambda2.run();                  // hoge
}

static <T> T cloneForce(T object) throws Exception {
    Method clone = Object.class.getDeclaredMethod("clone");
    clone.setAccessible(true);
    return (T) clone.invoke(object);
}

Serializable と同じように、状態を持つ外部変数も使ってみたけど、
こちらは参照が保たれたままで普通でした :)

public static void main(String[] args) throws Exception {
    Map<String, String> hoge = new HashMap<>();
    hoge.put("a", "x");
    Runnable lambda = (Runnable & Cloneable) () -> System.out.println(hoge);
    hoge.put("b", "y");
    Runnable lambda2 = cloneForce(lambda);
    hoge.put("c", "z");
    System.out.println(lambda);     // Hoge$$Lambda$1/455659002@7229724f
    lambda.run();                   // {a=x, b=y, c=z}
    System.out.println(lambda2);    // Hoge$$Lambda$1/455659002@4c873330
    lambda2.run();                  // {a=x, b=y, c=z}
}

以上!

役に立つかは微妙なところだけど、面白かったー。