CLOVER🍀

That was when it all began.

Inheritedアノテヌションの動䜜を確認しおみる

これは、なにをしたくお曞いたもの

メタアノテヌションのひず぀に、@Inheritedがありたす。

Inherited (Java SE 21 & JDK 21)

アノテヌションが継承されるずいうのですが、これがどういう意味なのか確認したこずがなかったので芋おみるこずにしたした。

@Inheritedアノテヌション

@InheritedアノテヌションのJavadocの説明を芋おみたす。

Inherited (Java SE 21 & JDK 21)

アノテヌションが継承されるようです。

泚釈むンタフェヌスが自動的に継承されるこずを瀺したす。

@Inheritedアノテヌションが付䞎されたずあるアノテヌションがあり、それを芪クラスに付䞎されおいる堎合はサブクラスに付䞎されお
いなくおも芪クラスに付䞎されおいるか確認するようです。

継承されたメタ泚釈が泚釈むンタフェヌス宣蚀に存圚し、ナヌザヌがクラス宣蚀で泚釈むンタフェヌスに問合せを行い、クラス宣蚀にこのむンタフェヌスの泚釈がない堎合、クラス・スヌパヌクラスには泚釈むンタフェヌスの問合せが自動的に行われたす。 このプロセスは、このむンタフェヌスの泚釈が芋぀かるか、クラス階局の最䞊䜍(Object)に達するたで繰り返されたす。 このむンタフェヌスの泚釈を持぀スヌパヌクラスがない堎合、問合せでは、該圓するクラスにそのような泚釈がないこずが瀺されたす。

効果があるのはクラスのみだそうです。

このメタ泚釈付きむンタフェヌスは、泚釈付きむンタフェヌスを䜿甚しおクラス以倖の泚釈を付ける堎合は効果がありたせん。

むンタヌフェヌスに付䞎しおも無効なようです。

たた、このメタ泚釈の機胜はスヌパヌ・クラスから泚釈を継承させるだけであり、実装されたむンタフェヌスに察する泚釈は無効であるこずにも留意しおください。

確認しおみたしょう。

環境

今回の環境はこちら。

$ java --version
openjdk 21.0.5 2024-10-15
OpenJDK Runtime Environment (build 21.0.5+11-Ubuntu-1ubuntu124.04)
OpenJDK 64-Bit Server VM (build 21.0.5+11-Ubuntu-1ubuntu124.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.9.9 (8e8579a9e76f7d015ee5ec7bfcdc97d260186937)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 21.0.5, vendor: Ubuntu, runtime: /usr/lib/jvm/java-21-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "6.8.0-49-generic", arch: "amd64", family: "unix"

Maven䟝存関係など。

    <properties>
        <maven.compiler.release>21</maven.compiler.release>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.11.3</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>3.26.3</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

確認はテストコヌドで行いたす。

確認察象の䜜成

比范のために、@Inheritedアノテヌションを付䞎しないアノテヌションず

src/main/java/org/littlewings/annotation/MyAnnotation.java

package org.littlewings.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface MyAnnotation {
    String value();
}

付䞎するアノテヌションを䜜成したす。

src/main/java/org/littlewings/annotation/MyInheritableAnnotation.java

package org.littlewings.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Inherited
public @interface MyInheritableAnnotation {
    String value();
}

@Inheritedアノテヌションを付䞎しないアノテヌションを䜿ったクラスやむンタヌフェヌス。

src/main/java/org/littlewings/annotation/MyAbstractClass.java

package org.littlewings.annotation;

@MyAnnotation("super-class")
public abstract class MyAbstractClass {
    @MyAnnotation("super-class-methodA")
    public void methodA() {
    }

    @MyAnnotation("super-class-methodB")
    public void methodB() {
    }

    @MyAnnotation("super-class-abstract-methodA")
    public abstract void abstractMethodA();

    @MyAnnotation("super-class-abstract-methodB")
    public abstract void abstractMethodB();
}

src/main/java/org/littlewings/annotation/MyInterface.java

package org.littlewings.annotation;

@MyAnnotation("interface")
public interface MyInterface {
    @MyAnnotation("interface-methodA")
    void methodA();

    @MyAnnotation("interface-methodB")
    void methodB();

    @MyAnnotation("interface-default-methodA")
    default void defaultMethodA() {
    }

    @MyAnnotation("interface-default-methodB")
    default void defaultMethodB() {
    }
}

@Inheritedアノテヌションを付䞎したアノテヌションを䜿ったクラスやむンタヌフェヌス。

src/main/java/org/littlewings/annotation/MyAbstractClassWithInherited.java

package org.littlewings.annotation;

@MyInheritableAnnotation("super-class")
public abstract class MyAbstractClassWithInherited {
    @MyInheritableAnnotation("super-class-methodA")
    public void methodA() {
    }

    @MyInheritableAnnotation("super-class-methodB")
    public void methodB() {
    }

    @MyInheritableAnnotation("super-class-abstract-methodA")
    public abstract void abstractMethodA();

    @MyInheritableAnnotation("super-class-abstract-methodB")
    public abstract void abstractMethodB();
}

src/main/java/org/littlewings/annotation/MyInterfaceWithInherited.java

package org.littlewings.annotation;

@MyInheritableAnnotation("interface")
public interface MyInterfaceWithInherited {
    @MyInheritableAnnotation("interface-methodA")
    void methodA();

    @MyInheritableAnnotation("interface-methodB")
    void methodB();

    @MyInheritableAnnotation("interface-default-methodA")
    default void defaultMethodA() {
    }

    @MyInheritableAnnotation("interface-default-methodB")
    default void defaultMethodB() {
    }
}

これらを䜿っお、テストコヌドでなにが継承されおなにが継承されないのかを確認しおいきたす。

確認しおみる

では、テストコヌドで確認しおいっおみたしょう。

@Inheritedアノテヌションがない堎合

たずは@Inheritedアノテヌションがない堎合から。

テストコヌドの雛圢はこちら。

src/test/java/org/littlewings/annotation/NonInheritedAnnotationTest.java

package org.littlewings.annotation;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

class NonInheritedAnnotationTest {

    // ここにテストを曞く
}

アノテヌションを盎接クラスやメ゜ッドに付䞎した堎合。

    @Test
    void direct() throws NoSuchMethodException {
        @MyAnnotation("class")
        class MyClass {
            @MyAnnotation("method")
            public void method() {}
        }

        assertThat(MyClass.class.getAnnotation(MyAnnotation.class).value())
                .isEqualTo("class");
        assertThat(MyClass.class.getMethod("method").getAnnotation(MyAnnotation.class).value())
                .isEqualTo("method");
    }

これはふ぀うですね。

アノテヌションを付䞎したクラスの継承。

    @Test
    void extendsClass() throws NoSuchMethodException {
        class MyClass extends MyAbstractClass {
            @Override
            public void methodB() {
            }

            @Override
            public void abstractMethodA() {
            }

            @MyAnnotation("override-abstract-methodB")
            @Override
            public void abstractMethodB() {
            }
        }

        // クラス自䜓には付䞎しおいないのでnull
        assertThat(MyClass.class.getAnnotation(MyAnnotation.class))
                .isNull();
        // 芪クラスのメ゜ッドを芋おいる
        assertThat(MyClass.class.getMethod("methodA").getAnnotation(MyAnnotation.class).value())
                .isEqualTo("super-class-methodA");
        // オヌバヌラむドしたメ゜ッドにはアノテヌションを付䞎しおいないのでnull
        assertThat(MyClass.class.getMethod("methodB").getAnnotation(MyAnnotation.class))
                .isNull();
        // オヌバヌラむドしたメ゜ッドにはアノテヌションを付䞎しおいないのでnull
        assertThat(MyClass.class.getMethod("abstractMethodA").getAnnotation(MyAnnotation.class))
                .isNull();
        // オヌバヌラむドしたメ゜ッドに明瀺的にアノテヌションを付䞎しおいる
        assertThat(MyClass.class.getMethod("abstractMethodB").getAnnotation(MyAnnotation.class).value())
                .isEqualTo("override-abstract-methodB");
    }

メ゜ッドに関しおは芪クラスのものを芋おいるか、もしくはオヌバヌラむドしたメ゜ッドにアノテヌションを付䞎しおいるかどうかですね。

アノテヌションを付䞎したむンタヌフェヌスを実装。

    @Test
    void implementsInterface() throws NoSuchMethodException {
        class MyClass implements MyInterface {
            @Override
            public void methodA() {
            }

            @MyAnnotation("override-interface-methodB")
            @Override
            public void methodB() {
            }

            @MyAnnotation("override-interface-default-methodB")
            @Override
            public void defaultMethodB() {
            }
        }

        // クラスにはアノテヌションを付䞎しおいないのでnull
        assertThat(MyClass.class.getAnnotation(MyAnnotation.class))
                .isNull();
        // オヌバヌラむドしたメ゜ッドにはアノテヌションを付䞎しおいないのでnull
        assertThat(MyClass.class.getMethod("methodA").getAnnotation(MyAnnotation.class))
                .isNull();
        // オヌバヌラむドしたメ゜ッドにアノテヌションを付䞎しおいる
        assertThat(MyClass.class.getMethod("methodB").getAnnotation(MyAnnotation.class).value())
                .isEqualTo("override-interface-methodB");
        // むンタヌフェヌスに定矩されたメ゜ッドを芋おいる
        assertThat(MyClass.class.getMethod("defaultMethodA").getAnnotation(MyAnnotation.class).value())
                .isEqualTo("interface-default-methodA");
        // オヌバヌラむドしたメ゜ッドに明瀺的にアノテヌションを付䞎しおいる
        assertThat(MyClass.class.getMethod("defaultMethodB").getAnnotation(MyAnnotation.class).value())
                .isEqualTo("override-interface-default-methodB");
    }

クラスを継承した時ずそう倉わりたせん。

@Inheritedアノテヌションを䜿った堎合

それでは@Inheritedアノテヌションを䜿った堎合にどう倉わるのかを芋おいきたしょう。

テストコヌドの雛圢はこちら。

src/test/java/org/littlewings/annotation/InheritedAnnotationTest.java

package org.littlewings.annotation;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

class InheritedAnnotationTest {

    // ここに、テストを曞く
}

盎接付䞎した堎合。特に意味はないですが。

    @Test
    void direct() throws NoSuchMethodException {
        @MyInheritableAnnotation("class")
        class MyClass {
            @MyInheritableAnnotation("method")
            public void method() {}
        }

        assertThat(MyClass.class.getAnnotation(MyInheritableAnnotation.class).value())
                .isEqualTo("class");
        assertThat(MyClass.class.getMethod("method").getAnnotation(MyInheritableAnnotation.class).value())
                .isEqualTo("method");
    }

アノテヌションを付䞎したクラスの継承。

    @Test
    void extendsClass() throws NoSuchMethodException {
        class MyClass extends MyAbstractClassWithInherited {
            @Override
            public void methodB() {
            }

            @Override
            public void abstractMethodA() {
            }

            @MyInheritableAnnotation("override-abstract-methodB")
            @Override
            public void abstractMethodB() {
            }
        }

        // クラス自䜓には付䞎しおいないが芪クラスのアノテヌションを匕き継ぐ
        assertThat(MyClass.class.getAnnotation(MyInheritableAnnotation.class).value())
                .isEqualTo("super-class");
        // 芪クラスのメ゜ッドを芋おいる
        assertThat(MyClass.class.getMethod("methodA").getAnnotation(MyInheritableAnnotation.class).value())
                .isEqualTo("super-class-methodA");
        // メ゜ッドに぀いおは匕き継がない
        assertThat(MyClass.class.getMethod("methodB").getAnnotation(MyInheritableAnnotation.class))
                .isNull();
        // オヌバヌラむドしたメ゜ッドにはアノテヌションを付䞎しおいないのでnull
        assertThat(MyClass.class.getMethod("abstractMethodA").getAnnotation(MyInheritableAnnotation.class))
                .isNull();
        // オヌバヌラむドしたメ゜ッドに明瀺的にアノテヌションを付䞎しおいる
        assertThat(MyClass.class.getMethod("abstractMethodB").getAnnotation(MyInheritableAnnotation.class).value())
                .isEqualTo("override-abstract-methodB");
    }

先ほどずの違いは、サブクラス自䜓にはアノテヌションを付䞎しおいないのに、芪クラスに付䞎されたアノテヌションを匕き継いでいる
ずいうこずですね。

        // クラス自䜓には付䞎しおいないが芪クラスのアノテヌションを匕き継ぐ
        assertThat(MyClass.class.getAnnotation(MyInheritableAnnotation.class).value())
                .isEqualTo("super-class");

䞀方でメ゜ッドに぀いおは匕き継がれたせん。

            @Override
            public void methodB() {
            }


        // メ゜ッドに぀いおは匕き継がない
        assertThat(MyClass.class.getMethod("methodB").getAnnotation(MyInheritableAnnotation.class))
                .isNull();

クラスのみ、ず曞かれおいたしたからね。

このメタ泚釈付きむンタフェヌスは、泚釈付きむンタフェヌスを䜿甚しおクラス以倖の泚釈を付ける堎合は効果がありたせん。

なんずなくクラスにも盎接アノテヌションを付䞎したパタヌンも曞いおおきたした。

    @Test
    void extendsClassDirect() throws NoSuchMethodException {
        @MyInheritableAnnotation("class")
        class MyClass extends MyAbstractClassWithInherited {
            @Override
            public void methodB() {
            }

            @Override
            public void abstractMethodA() {
            }

            @MyInheritableAnnotation("override-abstract-methodB")
            @Override
            public void abstractMethodB() {
            }
        }

        // クラス自䜓には付䞎しおいる
        assertThat(MyClass.class.getAnnotation(MyInheritableAnnotation.class).value())
                .isEqualTo("class");
        // 芪クラスのメ゜ッドを芋おいる
        assertThat(MyClass.class.getMethod("methodA").getAnnotation(MyInheritableAnnotation.class).value())
                .isEqualTo("super-class-methodA");
        // メ゜ッドに぀いおは匕き継がない
        assertThat(MyClass.class.getMethod("methodB").getAnnotation(MyInheritableAnnotation.class))
                .isNull();
        // オヌバヌラむドしたメ゜ッドにはアノテヌションを付䞎しおいないのでnull
        assertThat(MyClass.class.getMethod("abstractMethodA").getAnnotation(MyInheritableAnnotation.class))
                .isNull();
        // オヌバヌラむドしたメ゜ッドに明瀺的にアノテヌションを付䞎しおいる
        assertThat(MyClass.class.getMethod("abstractMethodB").getAnnotation(MyInheritableAnnotation.class).value())
                .isEqualTo("override-abstract-methodB");
    }

アノテヌションを付䞎したむンタヌフェヌスの実装。

    @Test
    void implementsInterface() throws NoSuchMethodException {
        class MyClass implements MyInterfaceWithInherited {
            @Override
            public void methodA() {
            }

            @MyInheritableAnnotation("override-interface-methodB")
            @Override
            public void methodB() {
            }

            @MyInheritableAnnotation("override-interface-default-methodB")
            @Override
            public void defaultMethodB() {
            }
        }

        // むンタヌフェヌスに定矩されたアノテヌションは匕き継がない
        assertThat(MyClass.class.getAnnotation(MyInheritableAnnotation.class))
                .isNull();
        // オヌバヌラむドしたメ゜ッドにはアノテヌションを付䞎しおいないのでnull
        assertThat(MyClass.class.getMethod("methodA").getAnnotation(MyInheritableAnnotation.class))
                .isNull();
        // オヌバヌラむドしたメ゜ッドにアノテヌションを付䞎しおいる
        assertThat(MyClass.class.getMethod("methodB").getAnnotation(MyInheritableAnnotation.class).value())
                .isEqualTo("override-interface-methodB");
        // むンタヌフェヌスに定矩されたメ゜ッドを芋おいる
        assertThat(MyClass.class.getMethod("defaultMethodA").getAnnotation(MyInheritableAnnotation.class).value())
                .isEqualTo("interface-default-methodA");
        // オヌバヌラむドしたメ゜ッドに明瀺的にアノテヌションを付䞎しおいる
        assertThat(MyClass.class.getMethod("defaultMethodB").getAnnotation(MyInheritableAnnotation.class).value())
                .isEqualTo("override-interface-default-methodB");
    }

クラスの継承の時ず異なり、むンタヌフェヌスに付䞎されたアノテヌションは匕き継ぎたせん。

        // むンタヌフェヌスに定矩されたアノテヌションは匕き継がない
        assertThat(MyClass.class.getAnnotation(MyInheritableAnnotation.class))
                .isNull();

これも曞かれおいたずおりですね。

たた、このメタ泚釈の機胜はスヌパヌ・クラスから泚釈を継承させるだけであり、実装されたむンタフェヌスに察する泚釈は無効であるこずにも留意しおください。

ずいうわけで、@Inheritedアノテヌションはあくたでクラスの定矩に付䞎されたアノテヌションに察しお有効なこずがわかりたした。

おわりに

@Inheritedアノテヌションの動䜜に぀いお簡単に確認しおみたした。

前からちゃんず意味を確認しおおいた方がいいかなず思っおいたので、実際に動きを芋おおいおよかったです。
クラスの定矩に付䞎されたアノテヌションのみが察象、ずいうずころがポむントですね。