シリアライザブルなラムダ式

ラムダ式は Serializable にできます。

//キャストしたり
Supplier<String> s = (Supplier<String> & Serializable) () -> "x";

//メソッドであれしたり
<T extends Supplier<String> & Serializable> void consume(T supplier) { ... }

で、シリアライズできるぞー、と思ってこんなコード書いて、

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.function.Supplier;

public class Sample {

    Supplier<String> get() {
        return (Supplier<String> & Serializable) () -> toString();
    }

    public static void main(String[] args) throws IOException {

        //↓シリアライザブルなサプライヤー
        Supplier<String> s = new Sample().get();

        //シリアライズしてみる
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try (ObjectOutputStream out = new ObjectOutputStream(baos)) {
            out.writeObject(s);
        }
    }
}

実行すると、

Exception in thread "main" java.io.NotSerializableException: Sample
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
    at java.io.ObjectOutputStream.writeArray(ObjectOutputStream.java:1378)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1174)
    at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
    at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
    at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
    at Sample.main(Sample.java:17)

例外です!

これはラムダ式で Sample クラスの toString メソッドを呼んでいるため Sample がキャプチャされますが、 Sample はSerializableでないため例外が出ます。

Supplier<String> get() {
    return (Supplier<String> & Serializable) () -> toString();
}

キャプチャというのはラムダ式内でラムダ式のスコープの外側の変数を参照した場合にラムダ式の実行環境に持ってくるっぽい感じのあれです。

キャプチャされているインスタンスは SerializedLambda から取ってこれます。 SerializedLambda はprivate finalな writeReplace メソッドで取ってこれます。 取ってこれるからと言ってカジュアルに呼んで良いメソッドではないです。 writeReplace については Serializable のJavadocに書いてあります。

こんな感じで SerializedLambda とキャプチャしたインスタンスを取ってこれました。

Supplier<String> s = new Sample().get();

Method m = s.getClass().getDeclaredMethod("writeReplace");
m.setAccessible(true);
SerializedLambda sl = (SerializedLambda) m.invoke(s);
for (int i = 0; i < sl.getCapturedArgCount(); i++) {
    System.out.println(sl.getCapturedArg(i));
}

この辺りをもっといじくりまわすと面白い事ができそうな気がしなくもないですね!

まとめもオチもない

とりあえず Sample クラスは Serializable をimplementsしましょう。

Gradleプラグインを書いて公開しちゃった

きっかけ

JAX-RSでWebフォントなどの静的リソースを返すリソースメソッドを書いたんですが、 ETagを使ってキャッシュをアレして転送量を節約したくなりました。

で、ETagにはMD5ハッシュ値を使ってたんですが毎回ハッシュ値を計算するのはあほくさいので、 ビルド時に計算してファイルに保存しておく事にしました。

その際にGradleプラグインを書いて、 どうせならと思って https://plugins.gradle.org/ で公開する事にしました。

Gradleプラグインを書く

次の公式ドキュメント(の日本語訳)を参考にすればオッケー!

Gradleプラグインを公開する

How do I add my plugin to the plugin portal? の通りに進めていけばオッケー!

ざっくり書くと、 まず アカウントを作って 、 自分のページでAPI Keyを作って、 それを ~/.gradle/gradle.properties に書いて、 build.gradlePlugin Publishing Plugin の設定を書いて、 gradle publishPlugins で公開します。

Gradleプラグインを書くときに知ってて良かったこと

知ってて良かったことっていうかGroovyの文法なんですけど、 次のようなことを知ってたらわりとスムーズにプラグインを書けました。

  • アクセサメソッドはフィールドアクセスのように書ける。
    • 例えば foo.getBar()foo.bar と書ける。
    • そして foo.setBar(baz)foo.bar = baz と書ける。
  • メソッド呼び出しで最後の引数がクロージャだと括弧の外に出せる。 つまり foo(bar, { x -> ... })foo(bar) { x -> ... } と書けて組み込みの構文のようにできる。
  • 引数がMapなら foo(bar: "...", baz: 123) みたいに書ける。
  • 展開演算子。 ['hoge', 'foo', 'x'].collect { it.length() }['hoge', 'foo', 'x']*.length() と書ける。
  • leftShift という名前のメソッドは << と書ける。 タスクを定義するときの task hoge << { ... }Task.leftShiftメソッド です。

Gradleプラグインを書くにあたって参考にしたもの

最初に挙げた公式ドキュメントの日本語訳はもちろん参考にしましたが、 他に GradleのAPIドキュメント が参考になりました。

特に次のクラスのドキュメントをよく読んだ気がします。

それから Gradleのソースコード も参考になりました。 特に JavaPluginWarPlugin を参考にしました。

まとめ

Gradleプラグインは書くのも公開するのもお手軽っぽいので、 これを読んで良いなと思ったらチャレンジしてみてくれさい!

ちなみに、こちらが私が書いたプラグインですどうぞ!