Lombokの@Wither(lombok.experimental.Wither)アノテーションの利用サンプルです。
Immutableなクラスでは、クラス内のメンバの値を変更したい場合にwithXXXXというメソッドを定義することがあります。
withメソッドは、元のインスタンスの内容は変更せず、指定の値を持つ新しいインスタンスを返します。
(例えば、LocalDate
クラスのLocalDate#withYear
等がこれに該当します)
@Wither
アノテーションを利用することでこのようなwithメソッドを簡単に定義することができます。
基本的に@Value
アノテーションと組み合わせて利用するのが良いと思います。
@Wither
は所属パッケージが"experimental"になっており、その名の通り実験的なアノテーションです。
そのため、今後Lombokのバージョンが上がった際などに仕様が変更される可能性もあると思われます。
クラスに@Witherを付与する
クラスに@Wither
アノテーションを付与すると、クラス内の各メンバに対応するwithメソッドが定義されます
■@Witherを付与したクラス
import lombok.Value; import lombok.experimental.Wither; @Value @Wither public class Person1 { private final long id; private final String name; private final String remarks; }
■利用側のコード
全メンバへに対するwithメソッドが利用できることが分かります。
public final class Person1Client { public static void main(String[] args) { long id = 1; String name = "Smple Taro"; String remarks = "Sample User"; Person1 srcPerson = new Person1(id, name, remarks); System.out.println("Origianl Person: " + srcPerson); System.out.println("------------------"); Person1 newPerson1 = srcPerson.withId(1000L); Person1 newPerson2 = srcPerson.withName("Ssmple Jiro"); Person1 newPerson3 = srcPerson.withRemarks("dummy User"); System.out.println("Origianl Person: " + srcPerson); System.out.println("New Person1: " + newPerson1); System.out.println("New Person2: " + newPerson2); System.out.println("New Person3: " + newPerson3); System.out.println("srcPerson == newPerson1 : " + (srcPerson == newPerson1)); System.out.println("newPerson1 == newPerson2 : " + (newPerson1 == newPerson2)); System.out.println("newPerson2 == newPerson3 : " + (newPerson2 == newPerson3)); } }
■実行結果
withXXXメソッドによって値を変更した場合、戻り値のインスタンスは元のインスタンスとは別物になっていることが分かります。
Origianl Person: Person1(id=1, name=Smple Taro, remarks=Sample User) ------------------ Origianl Person: Person1(id=1, name=Smple Taro, remarks=Sample User) New Person1: Person1(id=1000, name=Smple Taro, remarks=Sample User) New Person2: Person1(id=1, name=Ssmple Jiro, remarks=Sample User) New Person3: Person1(id=1, name=Smple Taro, remarks=dummy User) srcPerson == newPerson1 : false newPerson1 == newPerson2 : false newPerson2 == newPerson3 : false
■実際に生成されるソースコード
各メンバに対応するwithメソッドが生成されていることが分かります。
public final class Person1 { private final long id; private final String name; private final String remarks; public Person1(final long id, final String name, final String remarks) { this.id = id; this.name = name; this.remarks = remarks; } public long getId() { return this.id; } public String getName() { return this.name; } public String getRemarks() { return this.remarks; } @Override public boolean equals(final Object o) { if (o == this) return true; if (!(o instanceof Person1)) return false; final Person1 other = (Person1) o; if (this.getId() != other.getId()) return false; final Object this$name = this.getName(); final Object other$name = other.getName(); if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false; final Object this$remarks = this.getRemarks(); final Object other$remarks = other.getRemarks(); if (this$remarks == null ? other$remarks != null : !this$remarks.equals(other$remarks)) return false; return true; } @Override public int hashCode() { final int PRIME = 59; int result = 1; final long $id = this.getId(); result = result * PRIME + (int) ($id >>> 32 ^ $id); final Object $name = this.getName(); result = result * PRIME + ($name == null ? 43 : $name.hashCode()); final Object $remarks = this.getRemarks(); result = result * PRIME + ($remarks == null ? 43 : $remarks.hashCode()); return result; } @Override public String toString() { return "Person1(id=" + this.getId() + ", name=" + this.getName() + ", remarks=" + this.getRemarks() + ")"; } public Person1 withId(final long id) { return this.id == id ? this : new Person1(id, this.name, this.remarks); } public Person1 withName(final String name) { return this.name == name ? this : new Person1(this.id, name, this.remarks); } public Person1 withRemarks(final String remarks) { return this.remarks == remarks ? this : new Person1(this.id, this.name, remarks); } }
@Witherをメンバに付与する
値の変更を許可するメンバが限定したい状況も考えられます。
そのような場合は、@Wither
アノテーションを値の変更を許容するメンバに付与します。
■@Witherを付与したクラス
メンバのnameとremarksに@Witherを付与しています
import lombok.Value; import lombok.experimental.Wither; @Value public class Person2 { private final long id; @Wither private final String name; @Wither private final String remarks; }
■実際に生成されるソースコード
nameとremarksに対応するwithメソッドは定義されていますが、idに対応するwithメソッドは定義されていなことが分かります
public final class Person2 { private final long id; private final String name; private final String remarks; public Person2(final long id, final String name, final String remarks) { this.id = id; this.name = name; this.remarks = remarks; } public long getId() { return this.id; } public String getName() { return this.name; } public String getRemarks() { return this.remarks; } @Override public boolean equals(final Object o) { if (o == this) return true; if (!(o instanceof Person2)) return false; final Person2 other = (Person2) o; if (this.getId() != other.getId()) return false; final Object this$name = this.getName(); final Object other$name = other.getName(); if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false; final Object this$remarks = this.getRemarks(); final Object other$remarks = other.getRemarks(); if (this$remarks == null ? other$remarks != null : !this$remarks.equals(other$remarks)) return false; return true; } @Override public int hashCode() { final int PRIME = 59; int result = 1; final long $id = this.getId(); result = result * PRIME + (int) ($id >>> 32 ^ $id); final Object $name = this.getName(); result = result * PRIME + ($name == null ? 43 : $name.hashCode()); final Object $remarks = this.getRemarks(); result = result * PRIME + ($remarks == null ? 43 : $remarks.hashCode()); return result; } @Override public String toString() { return "Person2(id=" + this.getId() + ", name=" + this.getName() + ", remarks=" + this.getRemarks() + ")"; } public Person2 withName(final String name) { return this.name == name ? this : new Person2(this.id, name, this.remarks); } public Person2 withRemarks(final String remarks) { return this.remarks == remarks ? this : new Person2(this.id, this.name, remarks); } }
withメソッドのアクセスレベルを制御する
@Wither
を付与するとデフォルトで生成されるwithメソッドのアクセスレベルはpublicになります。
このアクセスレベルを変更したい場合は、パラメータにAccessLevel
を指定します。
■@Witherを付与したクラス
nameに対する@WitherでのアクセスレベルにAccessLevel.PROTECTEDを指定しています。
import lombok.AccessLevel; import lombok.Value; import lombok.experimental.Wither; @Value public class Person3 { private final long id; @Wither(AccessLevel.PROTECTED) private final String name; @Wither private final String remarks; }
■実際に生成されるソースコード
withName
(nameに対するwithメソッド)のアクセスレベルがprotectedになっていることが分かります。
public final class Person3 { private final long id; private final String name; private final String remarks; public Person3(final long id, final String name, final String remarks) { this.id = id; this.name = name; this.remarks = remarks; } public long getId() { return this.id; } public String getName() { return this.name; } public String getRemarks() { return this.remarks; } @Override public boolean equals(final Object o) { if (o == this) return true; if (!(o instanceof Person3)) return false; final Person3 other = (Person3) o; if (this.getId() != other.getId()) return false; final Object this$name = this.getName(); final Object other$name = other.getName(); if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false; final Object this$remarks = this.getRemarks(); final Object other$remarks = other.getRemarks(); if (this$remarks == null ? other$remarks != null : !this$remarks.equals(other$remarks)) return false; return true; } @Override public int hashCode() { final int PRIME = 59; int result = 1; final long $id = this.getId(); result = result * PRIME + (int) ($id >>> 32 ^ $id); final Object $name = this.getName(); result = result * PRIME + ($name == null ? 43 : $name.hashCode()); final Object $remarks = this.getRemarks(); result = result * PRIME + ($remarks == null ? 43 : $remarks.hashCode()); return result; } @Override public String toString() { return "Person3(id=" + this.getId() + ", name=" + this.getName() + ", remarks=" + this.getRemarks() + ")"; } protected Person3 withName(final String name) { return this.name == name ? this : new Person3(this.id, name, this.remarks); } public Person3 withRemarks(final String remarks) { return this.remarks == remarks ? this : new Person3(this.id, this.name, remarks); } }
nullの設定を許可しないようにする
@Wither
を付与して生成されるwithメソッドは、その引数にnullを指定できてしまいます。
nullを許可したくない場合は@NonNull
を組み合わせましょう。
■@Witherを付与したクラス
nameに対して@Witherと@NonNullを指定しています。
import lombok.NonNull; import lombok.Value; import lombok.experimental.Wither; @Value public class Person4 { private final long id; @Wither @NonNull private final String name; private final String remarks; }
■実際に生成されるソースコード
withName
(nameに対するwithメソッド)で引数のnullチェックが行われていることが分かります。
import lombok.NonNull; public final class Person4 { private final long id; @NonNull private final String name; private final String remarks; public Person4(final long id, @NonNull final String name, final String remarks) { if (name == null) { throw new NullPointerException("name"); } this.id = id; this.name = name; this.remarks = remarks; } public long getId() { return this.id; } @NonNull public String getName() { return this.name; } public String getRemarks() { return this.remarks; } @Override public boolean equals(final Object o) { if (o == this) return true; if (!(o instanceof Person4)) return false; final Person4 other = (Person4) o; if (this.getId() != other.getId()) return false; final Object this$name = this.getName(); final Object other$name = other.getName(); if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false; final Object this$remarks = this.getRemarks(); final Object other$remarks = other.getRemarks(); if (this$remarks == null ? other$remarks != null : !this$remarks.equals(other$remarks)) return false; return true; } @Override public int hashCode() { final int PRIME = 59; int result = 1; final long $id = this.getId(); result = result * PRIME + (int) ($id >>> 32 ^ $id); final Object $name = this.getName(); result = result * PRIME + ($name == null ? 43 : $name.hashCode()); final Object $remarks = this.getRemarks(); result = result * PRIME + ($remarks == null ? 43 : $remarks.hashCode()); return result; } @Override public String toString() { return "Person4(id=" + this.getId() + ", name=" + this.getName() + ", remarks=" + this.getRemarks() + ")"; } public Person4 withName(@NonNull final String name) { if (name == null) { throw new NullPointerException("name"); } return this.name == name ? this : new Person4(this.id, name, this.remarks); } }