Lombok Experimental features
LombokのExperimental featuresを試したメモです。
目次
- Lombok Experimental features
- @Accessors
- @FieldDefaults
- @Wither
- @XXX(onMethod= / onConstructor= / onParam=)
- @UtilityClass
- テストコード
Lombokは今や色んなところで使われているすごく便利なライブラリですが、
LombokのExperimental featuresも便利で少し使っていました。
使ったことない機能もあったので、試してみたメモです。
Lombok Experimental features
Lombokといえば、@Data、@Getter/@Setter、@NoArgsConstructor/@AllArgsConstructorなどコア機能はよく使われますが、
LombokにはExperimental featuresという機能もあります。
名前の通り、実験的な機能です。
コア機能よりまだ不安定だったり、致命的なバグがあったりする可能性があります。
IntelliJ Lombok Pluginでサポートされていない下記機能は試してないです。
・@ExtensionMethod
・@Delegate
・@Helper
maven
maven dependencyは普通にLombok使うのと同じです。
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.10</version> <scope>provided</scope> </dependency>
@Accessors
@Accessorsはgetterとsetterの生成と呼び出しを変更します。
chain
chain = trueの場合、setterはvoidの代わりにthisを返すようになります。
そのため、メソッドチェーンのように書けるようになります。
@Accessors(chain = true) public static class AccessorsChain { @Setter String bar; void printBar() { System.out.println("|" + this.bar + "|"); } }
テスト。
@Test public void AccessorsChainTest() { AccessorsChain chain = new AccessorsChain(); chain.setBar("AAA").printBar(); assertThat(chain.setBar("AAA").getClass()).isEqualTo(AccessorsChain.class); }
fluent
@Getterと@Setterを使うと、getterとsetterはそれぞれgetFoo()、setFoo(T newValue)となりますが、
fluent = trueの場合、getterとsetterはそれぞれfoo()、foo(T newValue)となります。
chainを指定しない場合は、同時にchain = trueとなります。
これで、スクリプト言語のようなメソッドチェーンができるようになります。
@Accessors(fluent = true) public static class AccessorsFluent { @Getter @Setter private String foo = "abc"; }
テスト。
@Test public void accessorsFluentTest() { AccessorsFluent acc = new AccessorsFluent(); // getter acc.foo(); assertThat(acc.foo()).isEqualTo("abc"); // setter acc.foo("ccc"); assertThat(acc.foo()).isEqualTo("ccc"); }
prefix
prefix = "xxx"を指定すると、プレフィックスを取り除いた名前でフィールドにアクセスできます。
public static class AccessorsPrefix { @Getter @Setter @Accessors(prefix = "pre") String preZoo; }
テスト。
setPreZoo()、getPreZoo()ではなく、setZoo()、getZoo()でアクセスできます。
@Test public void AccessorsPrefixTest() { AccessorsPrefix p = new AccessorsPrefix(); // getter p.setZoo("AAA"); // setter p.getZoo(); assertThat(p.getZoo()).isEqualTo("AAA"); }
@FieldDefaults
@FieldDefaultsはクラスやフィールドにアクセス修飾子を付与したり、finalを付与することができます。
level
level = AccessLevel.xxxxでアクセス修飾子を付与できます。
アクセス修飾子はlombok.AccessLevelのEnumを指定します。
PACKAGE、PRIVATE、PROTECTED、PUBLIC、MODULE、NONEを指定できます。
@Getter @FieldDefaults(level = AccessLevel.PRIVATE) public static class FieldLevelPrivate { String text = "ABC"; } @FieldDefaults(level = AccessLevel.PUBLIC) public static class FieldLevelPublic { int num = 100; }
テスト。
privateのフィールドは直接アクセスできず、publicのフィールドは直接アクセスできています。
@Test public void fieldPrivateTest() { FieldLevelPrivate pri = new FieldLevelPrivate(); // error!! // pri.text; assertThat(pri.getText()).isEqualTo("ABC"); } @Test public void fieldPublicTest() { FieldLevelPublic pub = new FieldLevelPublic(); assertThat(pub.num).isEqualTo(100); pub.num = 200; assertThat(pub.num).isEqualTo(200); }
makeFinal
makeFinal = trueを指定すると、フィールドをfinalにできます。
finalにしないフィールドがある場合、@NonFinalを指定します。
@FieldDefaults(makeFinal = true, level = AccessLevel.PUBLIC) public static class FieldFinal { int count = 200; @NonFinal String memo = "memorandum"; }
テスト。
finalなフィールドには再代入出来ていないことがわかります。
@NonFinalをつけたフィールドには再代入出来ています。
@Test public void FieldFinalTest() { FieldFinal ff = new FieldFinal(); // final // ff.num = 200; // error!! assertThat(ff.count).isEqualTo(200); // non final ff.memo = "new memo"; assertThat(ff.memo).isEqualTo("new memo"); }
@Wither
@Witherはfinalなフィールドを持つオブジェクトにおいて、新しいフィールド値を持つクローンを作成するメソッドを提供します。
fooフィールドに対して、fooに新しい値を設定したオブジェクトを返すwithFoo(newValue)メソッドが生成されます。
@Getter public class WitherExample { @Wither private final String name; @Wither private final int age; public WitherExample(String name, int age) { if (name == null) throw new NullPointerException(); this.name = name; this.age = age; } }
テスト。
withName("BBB")でnameにBBBを設定したクローンを生成します。
withAge(1_000)でageに1_000を設定したクローンを生成します。
@Test public void WitherTest() { WitherExample origin = new WitherExample("abc", 123); WitherExample newNameByWith = origin.withName("BBB"); assertThat(origin).isNotEqualTo(newNameByWith); assertThat(newNameByWith.getAge()).isEqualTo(123); assertThat(newNameByWith.getName()).isEqualTo("BBB"); WitherExample newAgeByWith = origin.withAge(1_000); assertThat(origin).isNotEqualTo(newAgeByWith); assertThat(newAgeByWith.getAge()).isEqualTo(1_000); assertThat(newAgeByWith.getName()).isEqualTo("abc"); }
@XXX(onMethod= / onConstructor= / onParam=)
onMethod、onConstructor、onParamは、Lombokが生成したコンストラクタ、メソッドにアノテーションを付与することができます。
onMethod
@Setter、@Getter、@Witherで生成されたメソッドにアノテーションを付与する場合、onMethodを使用します。
onConstructor
@AllArgsConstructor、@NoArgsConstructor、@RequiredArgsConstructorで生成されたコンストラクタに
アノテーションを付与する場合、onConstructorを使用します。
onParam
@Setter、@Witherで生成されたメソッドのパラメータにアノテーションを付与する場合、onParamを使用します。
付与したいアノテーションを指定する方法は、
onXxxx = @__({@Annotation1, @Annotation2})
という形で指定します。
@AllArgsConstructor(onConstructor = @__(@OnXExample.ConstructorAnnotation)) public class OnXExample { @Setter(onParam = @__({@Min(10), @Max(20)})) public String name; @Getter(onMethod = @__({@MethodAnnotation})) private int num; @NotNull @Target(METHOD) @Retention(RUNTIME) @interface MethodAnnotation { } @Target(CONSTRUCTOR) @Retention(RUNTIME) @interface ConstructorAnnotation { } }
テスト。
指定したアノテーションが付与されているかをリフレクションを使って確認。
@Test public void OnXTest() throws NoSuchMethodException { // constructor Constructor con = OnXExample.class.getConstructor(String.class, int.class); Annotation anoCons = con.getAnnotation(OnXExample.ConstructorAnnotation.class); assertThat(anoCons).isInstanceOf(OnXExample.ConstructorAnnotation.class); // setter Method name = OnXExample.class.getMethod("setName", String.class); Parameter[] paraSetter = name.getParameters(); Annotation anoSetter = paraSetter[0].getAnnotation(Min.class); assertThat(anoSetter).isInstanceOf(Min.class); // getter Method num = OnXExample.class.getMethod("getNum"); Annotation anoGetter = num.getAnnotation(OnXExample.MethodAnnotation.class); assertThat(anoGetter).isInstanceOf(OnXExample.MethodAnnotation.class); }
@UtilityClass
@UtilityClassはインスタンス化不可のユーティリティクラスを生成します。
@UtilityClassを付与すると、そのクラスはfinalになります。同時にすべてのメンバがstaticになります。
@UtilityClass public class UtilityExample { int MAGIC_NUMBER = 10; public int doubleNum(int num) { return num + num; } }
テスト。
インスタンス化不可。staticメンバーが呼び出せる。
@Test public void UtilityTest() { // error // UtilityExample util = new UtilityExample(); int magicNum = UtilityExample.MAGIC_NUMBER; assertThat(magicNum).isEqualTo(10); int doubleNum = UtilityExample.doubleNum(200); assertThat(doubleNum).isEqualTo(400); }
テストコード
今回のテストコードの全体です。
AccessorsExample.java
package lombok; import lombok.experimental.Accessors; public class AccessorsExample { @Accessors(chain = true) public static class AccessorsChain { @Setter String bar; void printBar() { System.out.println("|" + this.bar + "|"); } } @Accessors(fluent = true) public static class AccessorsFluent { @Getter @Setter private String foo = "abc"; } public static class AccessorsPrefix { @Getter @Setter @Accessors(prefix = "pre") String preZoo; } }
FieldDefaultsExample.java
package lombok; import lombok.experimental.FieldDefaults; import lombok.experimental.NonFinal; import lombok.experimental.Wither; class FieldDefaultsExample { @Getter @FieldDefaults(level = AccessLevel.PRIVATE) public static class FieldLevelPrivate { String text = "ABC"; } @FieldDefaults(level = AccessLevel.PUBLIC) public static class FieldLevelPublic { int num = 100; } @FieldDefaults(makeFinal = true, level = AccessLevel.PUBLIC) public static class FieldFinal { int count = 200; @NonFinal String memo = "memorandum"; } }
WitherExample.java
package lombok; import lombok.experimental.Wither; @Getter public class WitherExample { @Wither private final String name; @Wither private final int age; public WitherExample(String name, int age) { if (name == null) throw new NullPointerException(); this.name = name; this.age = age; } }
OnXExample.java
package lombok; import javax.validation.constraints.Max; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.CONSTRUCTOR; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; @AllArgsConstructor(onConstructor = @__(@OnXExample.ConstructorAnnotation)) public class OnXExample { @Setter(onParam = @__({@Min(10), @Max(20)})) public String name; @Getter(onMethod = @__({@MethodAnnotation})) private int num; @NotNull @Target(METHOD) @Retention(RUNTIME) @interface MethodAnnotation { } @Target(CONSTRUCTOR) @Retention(RUNTIME) @interface ConstructorAnnotation { } }
UtilityExample.java
package lombok; import lombok.experimental.UtilityClass; @UtilityClass public class UtilityExample { int MAGIC_NUMBER = 10; public int doubleNum(int num) { return num + num; } }
LombokExampleTest.java
package lombok; import org.junit.Test; import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; import javax.validation.constraints.Min; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import lombok.FieldDefaultsExample.FieldLevelPrivate; import lombok.FieldDefaultsExample.FieldLevelPublic; import lombok.FieldDefaultsExample.FieldFinal; import lombok.AccessorsExample.AccessorsChain; import lombok.AccessorsExample.AccessorsFluent; import lombok.AccessorsExample.AccessorsPrefix; import static org.assertj.core.api.Assertions.assertThat; @RunWith(Enclosed.class) public class LombokExampleTest { public static class AccessorsExampleTest { @Test public void AccessorsChainTest() { AccessorsChain chain = new AccessorsChain(); chain.setBar("AAA").printBar(); assertThat(chain.setBar("AAA").getClass()).isEqualTo(AccessorsChain.class); } @Test public void accessorsFluentTest() { AccessorsFluent acc = new AccessorsFluent(); // getter acc.foo(); assertThat(acc.foo()).isEqualTo("abc"); // setter acc.foo("ccc"); assertThat(acc.foo()).isEqualTo("ccc"); } @Test public void AccessorsPrefixTest() { AccessorsPrefix p = new AccessorsPrefix(); // getter p.setZoo("AAA"); // setter p.getZoo(); assertThat(p.getZoo()).isEqualTo("AAA"); } } public static class FieldDefaultsExampleTest { @Test public void fieldPrivateTest() { FieldLevelPrivate pri = new FieldLevelPrivate(); // error!! // pri.text; assertThat(pri.getText()).isEqualTo("ABC"); } @Test public void fieldPublicTest() { FieldLevelPublic pub = new FieldLevelPublic(); assertThat(pub.num).isEqualTo(100); pub.num = 200; assertThat(pub.num).isEqualTo(200); } @Test public void FieldFinalTest() { FieldFinal ff = new FieldFinal(); // final // ff.num = 200; // error!! assertThat(ff.count).isEqualTo(200); // non final ff.memo = "new memo"; assertThat(ff.memo).isEqualTo("new memo"); } } public static class WitherExampleTest { @Test public void WitherTest() { WitherExample origin = new WitherExample("abc", 123); WitherExample newNameByWith = origin.withName("BBB"); assertThat(origin).isNotEqualTo(newNameByWith); assertThat(newNameByWith.getAge()).isEqualTo(123); assertThat(newNameByWith.getName()).isEqualTo("BBB"); WitherExample newAgeByWith = origin.withAge(1_000); assertThat(origin).isNotEqualTo(newAgeByWith); assertThat(newAgeByWith.getAge()).isEqualTo(1_000); assertThat(newAgeByWith.getName()).isEqualTo("abc"); } } public static class onXExampleTest { @Test public void OnXTest() throws NoSuchMethodException { // constructor Constructor con = OnXExample.class.getConstructor(String.class, int.class); Annotation anoCons = con.getAnnotation(OnXExample.ConstructorAnnotation.class); assertThat(anoCons).isInstanceOf(OnXExample.ConstructorAnnotation.class); // setter Method name = OnXExample.class.getMethod("setName", String.class); Parameter[] paraSetter = name.getParameters(); Annotation anoSetter = paraSetter[0].getAnnotation(Min.class); assertThat(anoSetter).isInstanceOf(Min.class); // getter Method num = OnXExample.class.getMethod("getNum"); Annotation anoGetter = num.getAnnotation(OnXExample.MethodAnnotation.class); assertThat(anoGetter).isInstanceOf(OnXExample.MethodAnnotation.class); } } public static class UtilityExampleTest { @Test public void UtilityTest() { // error // UtilityExample util = new UtilityExample(); int magicNum = UtilityExample.MAGIC_NUMBER; assertThat(magicNum).isEqualTo(10); int doubleNum = UtilityExample.doubleNum(200); assertThat(doubleNum).isEqualTo(400); } } }
終わり。
ソースは一応上げときました。