LoginSignup
12
10

More than 5 years have passed since last update.

@lombok.Builder と Optional の狭間

Last updated at Posted at 2016-03-08

@lombok.Builderの不満点

@lombok.Builderは無かったら超困る程度には便利だが, 不満点もある. それはOptionalとの相性の悪さだ.

以下のようなデータ保持クラスがあるとしよう.

ValueClass.java
@Value
@Builder
public class ValueClass {
    private final @Nonnull String requiredField;
    private final Optional<String> optionalField;
}

requiredFieldは必須フィールド, optionalFieldは空かもしれないフィールドとする.
こいつのBuilderを呼ぶとき, optionalFieldに空を渡したい場合, 以下のように書きたいだろう.

ValueClassTest.java
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自体にデフォルト値を与えようなどと考え, 以下のようなことをしてみるのだが, 上手く行かない.

ValueClass.java
@Value
@Builder
public class ValueClass {
    private final @Nonnull String requiredField;
    private Optional<String> optionalField = Optional.empty(); // こんな感じで初期値を与えたい
}

これをやると, そもそもValueClassのBuilderにoptionalFiledのセッタが生成されなくなってしまう. 結局のところ, Builderの呼び出し側コードでセッタにOptional.empty()を渡すしかない. つらい.

さらに, Builderに空でない値を渡す場合も, 以下のようなコードになり, 嬉しくない.

ValueClassTest.java
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に適用すると, 以下のようになる.

ValueClass.java
@Value
@Builder
public class ValueClass {
    private final @Nonnull String requiredField;
    private final String optionalField;

    public Optional<String> getOptionalField() {
        return Optional.ofNullable(optionalField);
    }
}

呼び出し側のコードは, 以下のようになる.

ValueClassTest.java
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) とかが必要になる.
12
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
10