☕️
Javaのレコードについてメモ:アノテーション
レコードについて軽く調べてメモっておくシリーズ。
今回はアノテーションについてです。
環境
$ java --version
java 17 2021-09-14 LTS
Java(TM) SE Runtime Environment (build 17+35-LTS-2724)
Java HotSpot(TM) 64-Bit Server VM (build 17+35-LTS-2724, mixed mode, sharing)
アノテーション付与パターン
コンポーネントの宣言にアノテーションを付けるとCanonical Constructorの引数とフィールド、アクセサーメソッドのすべてに同じアノテーションが付与されるようです。
アノテーションが付与される箇所をコントロールしたい場合は次のようにすると良さそうです。
ケース | コンストラクタ引数 | フィールド | アクセサー | 方法 |
---|---|---|---|---|
1 | ○ | ○ | ○ | コンポーネントの宣言にアノテーションを付ける |
2 | ○ | ○ | - | コンポーネントの宣言にアノテーションを付けてアクセサーを明示的に宣言する(アクセサーにはアノテーションを付けない) |
3 | ○ | - | ○ | コンストラクターとアクセサーを明示的に宣言してコンストラクター引数とアクセサーにアノテーションを付ける |
4 | - | ○ | ○ | コンポーネントの宣言にアノテーションを付けてコンストラクターを明示的に宣言する(コンストラクター引数にはアノテーションを付けない) |
5 | ○ | - | - | コンストラクターを明示的に宣言してコンストラクター引数にアノテーションを付ける |
6 | - | ○ | - | コンポーネントの宣言にアノテーションを付けてコンストラクターとアクセサーを明示的に宣言する(コンストラクター引数とアクセサーにはアノテーションを付けない) |
7 | - | - | ○ | アクセサーを明示的に宣言してアノテーションを付ける |
アノテーション付与パターンの確認用コード
package example.record;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.Comparator;
public class RecordAnnotationPatternDemo {
record Case1(@Foobar String component) {
}
record Case2(@Foobar String component) {
public String component() {
return component;
}
}
record Case3(String component) {
Case3(@Foobar String component) {
this.component = component;
}
@Foobar
public String component() {
return component;
}
}
record Case4(@Foobar String component) {
Case4(String component) {
this.component = component;
}
}
record Case5(String component) {
Case5(@Foobar String component) {
this.component = component;
}
}
record Case6(@Foobar String component) {
Case6(String component) {
this.component = component;
}
public String component() {
return component;
}
}
record Case7(String component) {
@Foobar
public String component() {
return component;
}
}
@Retention(RetentionPolicy.RUNTIME)
@interface Foobar {
}
public static void main(String[] args) throws ReflectiveOperationException {
for (Class<?> clazz : Arrays.stream(RecordAnnotationPatternDemo.class.getDeclaredClasses())
.filter(Class::isRecord)
.sorted(Comparator.comparing(Class::getSimpleName))
.toList()) {
inspect(clazz);
}
}
static void inspect(Class<?> clazz) throws ReflectiveOperationException {
String componentName = "component";
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class);
Parameter constructorParameter = constructor.getParameters()[0];
Field field = clazz.getDeclaredField(componentName);
Method accessor = clazz.getDeclaredMethod(componentName);
System.out.printf("%s %s %s %s%n", clazz.getSimpleName(),
hasAnnotation(constructorParameter),
hasAnnotation(field),
hasAnnotation(accessor));
}
static String hasAnnotation(AnnotatedElement element) {
return element.getDeclaredAnnotation(Foobar.class) != null ? "o" : "-";
}
}
アノテーションの@TargetとElementType
アノテーションは@Target
で付与できる対象(ElementType
)を設定できます。
レコードのコンポーネントに付与するアノテーションは次のElementType
を対象にできるようです。
RECORD_COMPONENT
PARAMETER
FIELD
METHOD
TYPE_USE
これらのElementType
を@Target
に持つアノテーションを5つ用意し、それぞれコンポーネントに付与したレコードをリフレクションで調べてみました。
そうするとPARAMETER
/FIELD
/METHOD
はそれぞれコンストラクター引数/フィールド/アクセサーメソッドにアノテーションが付いていました。
RECORD_COMPONENT
(とTYPE_USE
)はそれらにアノテーションは付いていませんでした。
ただし、Class
クラスにgetRecordComponents
というメソッドが追加されており、それで得たjava.lang.reflect.RecordComponent
からはアノテーションを取得できました。
RecordComponent
からはアクセサーメソッドも取得できるみたいです。
なるほど。
ElementTypeとアノテーション付与の確認用コード
package example.record;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.RecordComponent;
import java.util.Arrays;
import java.util.Comparator;
public record RecordAnnotationTargetDemo(@Case1 @Case2 @Case3 @Case4 @Case5 String component) {
@Target({ ElementType.RECORD_COMPONENT })
@Retention(RetentionPolicy.RUNTIME)
@interface Case1 {
}
@Target({ ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@interface Case2 {
}
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@interface Case3 {
}
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@interface Case4 {
}
@Target({ ElementType.TYPE_USE })
@Retention(RetentionPolicy.RUNTIME)
@interface Case5 {
}
@SuppressWarnings("unchecked")
public static void main(String[] args) throws ReflectiveOperationException {
for (Class<?> clazz : Arrays.stream(RecordAnnotationTargetDemo.class.getDeclaredClasses())
.filter(Class::isAnnotation)
.sorted(Comparator.comparing(Class::getSimpleName))
.toList()) {
inspect((Class<? extends Annotation>) clazz);
}
}
static void inspect(Class<? extends Annotation> annotation) throws ReflectiveOperationException {
Class<?> clazz = RecordAnnotationTargetDemo.class;
RecordComponent component = clazz.getRecordComponents()[0];
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class);
Parameter constructorParameter = constructor.getParameters()[0];
Field field = clazz.getDeclaredField(component.getName());
Method accessor = component.getAccessor();
System.out.printf("%s %s %s %s %s%n", annotation.getSimpleName(),
hasAnnotation(annotation, component),
hasAnnotation(annotation, constructorParameter),
hasAnnotation(annotation, field),
hasAnnotation(annotation, accessor));
}
static String hasAnnotation(Class<? extends Annotation> annotation, AnnotatedElement element) {
return element.getDeclaredAnnotation(annotation) != null ? "o" : "-";
}
}
以上です。
Discussion