arnoldというJavaのbuilderライブラリを書いた
今時builderライブラリも無いだろという感じですが,書いたのです.
Motivationにも書いているのですが,普通に作ったJavaのbuilderは不完全なインスタンスを作成できる可能性を孕んでいます.注意深くコードを書けば問題ないかもしれませんが,えてして我々 (主語が大きい) はミスを犯しがちです.そしてNPEがやってきて……我々はしぬ.
というわけで今回作ったのがarnoldです.
端的にいうと「必要なすべての要素を取りこぼさないbuilder」であり,「そのジェネレータ (annotation processor)」でもあります.Basic usageにあるように,
import net.moznion.arnold.annotation.ArnoldBuilder; import net.moznion.arnold.annotation.Required; @ArnoldBuilder public class Example { @Required private String foo; @Required private int bar; @Required private double buz; }
というようにclassに @ArnoldBuilder
を付与することでinstantiate対象のclassであることを示し,builderでbuildしたいフィールドについては @Required
を付与することで必要なフィールドであることを示すことができます.そしてプロジェクトをビルド *1 するとAnnotation Processorが走り,builder classが自動生成されます.
自動生成されたbuilderはこのように使うことができます.
Example example = new ExampleBuilder().foo("foo") .bar(42) .buz(2.71828) .build();
このBuilderは分解して書いてみると
ExampleBuilder exampleBuilder = new ExampleBuilder(); ExampleBuilder1 exampleBuilder1 = exampleBuilder.foo("foo"); ExampleBuilder2 exampleBuilder2 = exampleBuilder1.bar(42); ExampleBuilder3 exampleBuilder3 = exampleBuilder2.buz(2.71828); Example example = exampleBuilder3.build();
というふうになっており,つまり
ExampleBuilder
はfoo
を受け取ってExampleBuilder1
を返すsetterを提供するExampleBuilder1
はbar
を受け取ってExampleBuilder2
を返すsetterを提供するExampleBuilder2
はbuz
を受け取ってExampleBuilder3
を返すsetterを提供するExampleBuilder2
はbuild()
を提供する
というようなコードをAnnotation Processorが一式生成するという挙動となっています.なかなか富豪的ですね.
このように,1つのbuilder (もはやbuilderではない気もしますが) が高々1つの要素を受け取り次のbuilderを返却し,終端のbuilderが build()
して所望のclassのインスタンスをinstanciateする,という仕組みにすることで
- 「build途中の不完全な状態のインスタンス」は何らかのbuilder classになるので,build対象のインスタンスをinstantiateすることは出来ない
- 必須なフィールドがすべて揃ったらその時点でbuilderは
build()
を提供し,ようやくbuild対象のインスタンスをinstantiate出来るようになる
という安全な挙動を実現しています.
今後の課題としてはprivate classのinstantiateができないというものがあり,これはなんとかしたい *2.
それはそうと今回Javaのコード生成には square/javapoet を使ってみたんですが,使いやすく,なおかつパワフルで良いですね.
現場からは以上です.ぜひご利用下さい.