@lombok.Builder
の不満点
@lombok.Builder
は無かったら超困る程度には便利だが, 不満点もある. それはOptional
との相性の悪さだ.
以下のようなデータ保持クラスがあるとしよう.
@Value
@Builder
public class ValueClass {
private final @Nonnull String requiredField;
private final Optional<String> optionalField;
}
requiredField
は必須フィールド, optionalField
は空かもしれないフィールドとする.
こいつのBuilderを呼ぶとき, optionalField
に空を渡したい場合, 以下のように書きたいだろう.
public class ValueClassTest{
@Test
public void testBuilder() {
// optionalField にわざわざ Optional.empty() を指定したくない
val vo = ValueClass.builder()
.requiredField("required")
.build();
assertThat(vo.getRequiredField(), is("required"));
assertThat(vo.getOptionalField(), is(Optional.empty())); // このassertは通らない.
}
}
しかし, このようにBuilderを呼び出すと, optionalField
には無情にもnull
が入ってしまう. そこでValueClass
自体にデフォルト値を与えようなどと考え, 以下のようなことをしてみるのだが, 上手く行かない.
@Value
@Builder
public class ValueClass {
private final @Nonnull String requiredField;
private Optional<String> optionalField = Optional.empty(); // こんな感じで初期値を与えたい
}
これをやると, そもそもValueClass
のBuilderにoptionalFiled
のセッタが生成されなくなってしまう. 結局のところ, Builderの呼び出し側コードでセッタにOptional.empty()
を渡すしかない. つらい.
さらに, Builderに空でない値を渡す場合も, 以下のようなコードになり, 嬉しくない.
public class ValueClassTest {
@Test
public void testBuilder() {
val vo = ValueClass.builder()
.requiredField("required")
.optionalField(Optional.of("optional")) // 本当は .optionalField("optional") と書きたい
.build();
assertThat(vo.getRequiredField(), is("required"));
assertThat(vo.getOptionalField(), is(Optional.of("optional")));
}
}
Optional.of
, じゃあないんだよ. 型で空許容を明示して幸せな世界にしようとしたのに, Builderの呼び出しがOptional.of
だらけになって, なんだか逆に不幸になってしまった.
解決策
妥協の末, 下記のように折り合いをつけた.
-
@Builder
をつけるクラスのフィールドにはOptional
型を使わない. - 必須フィールドには
@Nonnull
をつける. - 空許容フィールドでは, 空表現を
null
で行う. - すべての
@Nonnull
がついていないフィールドのゲッタをOptional
型を返すように自作する.
先ほどのValueClass
に適用すると, 以下のようになる.
@Value
@Builder
public class ValueClass {
private final @Nonnull String requiredField;
private final String optionalField;
public Optional<String> getOptionalField() {
return Optional.ofNullable(optionalField);
}
}
呼び出し側のコードは, 以下のようになる.
public class ValueClassTest {
@Test
public void testBuilderEmpty() {
val vo = ValueClass.builder()
.requiredField("required")
.build();
assertThat(vo.getRequiredField(), is("required"));
assertThat(vo.getOptionalField(), is(Optional.empty()));
}
@Test
public void testBuilderNotEmpty() {
val vo = ValueClass.builder()
.requiredField("required")
.optionalField("optional")
.build();
assertThat(vo.getRequiredField(), is("required"));
assertThat(vo.getOptionalField(), is(Optional.of("optional")));
}
}
少しだけ, 幸せな世界に近づいた気がする.
デメリット
- 空許容フィールドが多いクラスの定義がつらい.
- フィールド名が変わったとき, 自作のゲッタも合わせて改名しないといけない.
- Builderに対して
Optional
型を渡せない.- 明示的な空表現のために
null
が登場する... - 全部のクラスをこのルールで作ってみると, 空許容フィールドの同士の受け渡しで
vo.getOptionalField().orElse(null)
とかが必要になる.
- 明示的な空表現のために