web-dev-qa-db-ja.com

Lombokビルダーでコンストラクターの後にコードを実行する方法

Lombok.Builderを使用したいクラスがあり、いくつかのパラメーターの前処理が必要です。このようなもの:

_@Builder
public class Foo {
   public String val1;
   public int val2;
   public List<String> listValues;

   public void init(){
       // do some checks with the values.
   }
}
_

通常、私はNoArgコンストラクターでinit()を呼び出すだけですが、生成されたビルダーではできません。生成されたビルダーがこのinitを呼び出す方法はありますか?たとえば、build()は次のようなコードを生成します。

_public Foo build() {
   Foo foo = Foo(params....)
   foo.init();
   return foo;
}
_

_all args_コンストラクターを手動でコーディングでき、Builderがそれを介して呼び出し、その中でinitを呼び出すことができることを知っています。

しかし、私のクラスではたまに新しいフィールドが時々追加されるため、これは次善のソリューションです。これは、コンストラクタも変更することを意味します。

14
Budius

試行錯誤の末、適切な解決策を見つけました。生成ビルダーを拡張し、init()を自分で呼び出します。

例:

@Builder(toBuilder = true, builderClassName = "FooInternalBuilder", builderMethodName = "internalBuilder")
public class Foo {

   public String val1;
   public int val2;
   @Singular public List<String> listValues;

   void init() {
      // perform values initialisation
   }

   public static Builder builder() {
      return new Builder();
   }

   public static class Builder extends FooInternalBuilder {

      Builder() {
         super();
      }

      @Override public Foo build() {
         Foo foo = super.build();
         foo.init();
         return foo;
      }
   }
}
11
Budius

Fooでは、コンストラクターを手動で追加し、初期化を行い、@Builderコンストラクタで。あなたはすでにこれを知っていることを知っていますが、それは正しい解決策だと思います。とにかくビルダーでコードを使用したいので、パラメーターを追加することを忘れないでください。

開示:私はロンボクの開発者です。

5
Roel Spilker

私は同じ問題に出くわしました。しかし、さらに、メソッドbuildOptional()をビルダーに追加して、必要になるたびにOptional.of(Foo)を繰り返さないようにしたかったのです。チェーンされたメソッドはFooInternalBuilderオブジェクトを返すため、これは以前に投稿されたアプローチでは機能しませんでした。 buildOptional()FooInternalBuilderに入れると、Builderinit()メソッドの実行が失敗します...

また、個人的には、2つのビルダークラスの存在が気に入らなかった。

これが代わりに私がしたことです:

_@Builder(buildMethodName = "buildInternal")
@ToString
public class Foo {
    public String val1;
    public int val2;
    @Singular  public List<String> listValues;

    public void init(){
        // do some checks with the values.
    }    

    /** Add some functionality to the generated builder class */
    public static class FooBuilder {
        public Optional<Foo> buildOptional() {
            return Optional.of(this.build());
        }

        public Foo build() {
            Foo foo = this.buildInternal();
            foo.init();
            return foo;
        }
    }
}
_

この主な方法で簡単なテストを行うことができます:

_public static void main(String[] args) {
    Foo foo = Foo.builder().val1("String").val2(14)
            .listValue("1").listValue("2").build();
    System.out.println(foo);

    Optional<Foo> fooOpt = Foo.builder().val1("String").val2(14)
            .listValue("1").listValue("2").buildOptional();
    System.out.println(fooOpt);
}
_

そうすることで、私が欲しいものを追加しましょう:

  • 各オブジェクトの構築後に自動的に実行されるinit()メソッドを追加します
  • 新しいフィールドを追加しても、追加の作業は必要ありません(個別に作成されたコンストラクターの場合と同様)。
  • 追加機能を追加する可能性(init()実行を含む)
  • _@Builder_アノテーションがもたらす完全な標準機能を保持する
  • 追加のビルダークラスを公開しない

これを解決策として共有する前に問題を解決したとしても。それは少し短く、(私にとって)Nice機能を追加します。

5
J.R.

これは私にとっては機能しますが、完全なソリューションではありませんが、すばやく簡単です。

@Builder
@AllArgsConstructor
public class Foo {
   @Builder.Default
   int bar = 42;
   Foo init() {
      // perform values initialisation
     bar = 451;   // replaces 314
     return foo;
   }
   static Foo test() {
       return new FooBuilder()  // defaults to 42
           .bar(314)  // replaces 42 with 314
           .build()
           .init();   // replaces 314 with 451
   }
}
0
Marc