値オブジェクトを作るのに Lombok で楽ができそう?

ぼけーっとLombok眺めてて。Valueって便利そうかもなぁって思った。

@Value

projectlombok.org

  • 全部のフィールドが private & final になるよ。
  • クラスも final になるよ。
  • setterは生成されないよ。
  • getterメソッドは生成されるよ。
  • フィールド初期化用のパラメータを持ったコンストラクターが生成されるよ。
  • toString(), equals() and hashCode() が生成されるよ。

JuiceId

例えばジュースのIDのValueObjectを定義したいなーってときは

import lombok.Value;

@Value
public class JuiceId {
  int id;
}

こう書いておけば、こんなクラスが生成される

import java.beans.ConstructorProperties;

public final class JuiceId {
  private final int id;

  @ConstructorProperties({"id"})
  public JuiceId(int id) {
    this.id = id;
  }

  public int getId() {
    return this.id;
  }

  public boolean equals(Object o) {
    if(o == this) {
      return true;
    } else if(!(o instanceof JuiceId)) {
      return false;
    } else {
      JuiceId other = (JuiceId)o;
      return this.getId() == other.getId();
    }
  }

  public int hashCode() {
    boolean PRIME = true;
    byte result = 1;
    int result1 = result * 59 + this.getId();
    return result1;
  }

  public String toString() {
    return "JuiceId(id=" + this.getId() + ")";
  }
}

動きはこんな感じか

import spock.lang.Specification
import spock.lang.Unroll

class JuiceIdSpec extends Specification {

  @Unroll
  def "new JuiceId(#a).equals(new JuiceId(#b)) == #c"() {
    expect:
      new JuiceId(a).equals(new JuiceId(b)) == c

    where:
      a | b || c
      1 | 1 || true
      1 | 2 || false
  }

  @Unroll
  def "new JuiceId(#a).getId() == #a"() {
    expect:
      new JuiceId(a).getId() == a

    where:
      _| a
      _| 1
      _| 2
  }

}

fluent

個人的にfluentが好きなのでAccessorsアノテーション使って

import lombok.Value;
import lombok.experimental.Accessors;

@Accessors(fluent = true)
@Value
public class FluentJuiceId {
  int id;
}

こうなるのが好き。Experimentalだけどね。

import java.beans.ConstructorProperties;

public final class FluentJuiceId {
  private final int id;

  @ConstructorProperties({"id"})
  public FluentJuiceId(int id) {
    this.id = id;
  }

  public int id() {
    return this.id;
  }

  public boolean equals(Object o) {
    if(o == this) {
      return true;
    } else if(!(o instanceof FluentJuiceId)) {
      return false;
    } else {
      FluentJuiceId other = (FluentJuiceId)o;
      return this.id() == other.id();
    }
  }

  public int hashCode() {
    boolean PRIME = true;
    byte result = 1;
    int result1 = result * 59 + this.id();
    return result1;
  }

  public String toString() {
    return "FluentJuiceId(id=" + this.id() + ")";
  }
}
import spock.lang.Specification
import spock.lang.Unroll

class FluentJuiceIdSpec extends Specification {

  @Unroll
  def "new FluentJuiceId(#a).id() == #a"() {
    expect:
      new FluentJuiceId(a).id() == a

    where:
      _| a
      _| 1
      _| 2
  }

}

Accessors

projectlombok.org

fluentオプション(boolean)
trueの場合は pepper の getter は単に pepper() になって、setter は pepper(T newValue)になるよ。あと、chainオプションもtrueになるよ。

chainオプション
trueの場合は生成されたセッターはvoidじゃなくてreturn thisするよ。

なんだけど

@Value 使うかなぁ?どうかなぁ?って考えてみて、やっぱ使わなさそうって思った。

細かい初期化をしたいときが多いし、値オブジェクトを定義するときにフィールドをfinalにする必要もないかなって思うので、 @EqualsAndHashCode + @Getter + @ToString くらいにしそうだな。それと @Accessors(fluent=true)。

ま、でも面白かったや。さて、娘達とクッキーでも作ろっと。