为什么@Repeatable注解不能从接口继承?

3
在Java中,标记为@Inherited的注解仅在注释类时起作用:

请注意,如果注释类型用于注释除类以外的任何内容,则此元注释类型无效。还要注意,此元注释仅导致从超类继承注释; 实现接口上的注释无效。

因此,使用@Inherited注解的接口或方法将不会导致实现类/方法也被注解。这样做的原因很可能是,如果类层次结构中有多个注解,则编译器不知道选择哪个注解,如此处所述。
现在,Java 8引入了新的注解@Repeatable。我认为,对于同时被标记为@Inherited@Repeatable的注解,删除以上限制是很自然的,因为编译器应该能够将冲突的注解添加到@Repeatable注解中。
以下是一个示例:
import java.lang.annotation.Inherited;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
@interface RepeatableAnnotations {
    RepeatableAnnotation[] value(); 
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
@Repeatable(RepeatableAnnotations.class)
@interface RepeatableAnnotation {
    String value();
}

@RepeatableAnnotation("A")
interface IntefaceA {}

@RepeatableAnnotation("B")
interface IntefaceB {}

@RepeatableAnnotation("C")
@RepeatableAnnotation("D")
public class TestClass implements IntefaceA, IntefaceB {
    public static void main(String[] args) {
        for (RepeatableAnnotation a : TestClass.class.getAnnotation(RepeatableAnnotations.class).value()) {
            System.out.print(a.value());
        }
    }
}

我本希望输出结果为ABCD,但实际结果只有CD(即@Inherited的表现和Java 8之前一样)。

有没有人知道在Java 8中,@Repeatable注解的情况下,为什么不取消接口和方法上@Inherited的限制?是否有克服上述类型层次结构以获得ABCD输出的任何解决方法?(除了使用反射扫描超级接口以查找注解...)

2个回答

4
请回忆一下@Inherited的文档:
如果在注解类型声明中存在Inherited元注释,并且用户查询类声明中的注解类型,而该类声明没有该类型的注解,则将自动查询该类的超类以获取注解类型。
换句话说,@Inherited从未旨在成为收集类型层次结构上多个注解的功能。相反,您将获得具有显式注解的最特定类型的注解。
换句话说,如果您将声明更改为
@RepeatableAnnotation("FOO") @RepeatableAnnotation("BAR") class Base {}

@RepeatableAnnotation("C") @RepeatableAnnotation("D")
public class TestClass extends Base implements IntefaceA, IntefaceB {

它不会改变结果;Base类的FOOBAR不会被TestClass继承,因为它具有显式注释值CD
将这个概念扩展到interface层次结构中可能会很麻烦,因为存在多重继承,并且超级接口可能会变成另一个超级接口的子接口,因此找到最具体的接口并不容易。这与超类层次结构的线性搜索有很大不同。
您可能会遇到多个不相关的已注释interface,但不清楚为什么应该通过将它们合并为一个重复的注释来解决这种歧义。这在所有其他情况下的行为都不协调。
请注意,您链接的答案是有些奇怪的,因为它显示使用方法注释的代码,但无论您是否指定了@Inherited都不会继承方法注释(在我的看法中,审计工具应该在将@Target(ElementType.METHOD)@Inherited组合时生成警告)。@Inherited只适用于类型注释。

非常感谢您详细的回答。由于多重继承和超级接口可能会变成另一个超级接口的子接口,因此将其扩展到接口层次结构会很麻烦,因此找到最具体的接口并不是一件容易的事情。您是正确的 - 我没有考虑到这种可能性。无论如何,我真的很想在Java中看到类似于@Inherited注释的接口和方法特性。 - Balder

0

但我在搜索类似解决方案时找到了这个线程。最终,我编写了这个辅助方法:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Labels
{
    Label[] value();
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(Labels.class)
public @interface Label
{
    String value();
}

@Label("A")
class A
{
}

@Label("B")
class B extends A
{
}

@Test
void LabelStackTest()
{

    var labels = ClassUtils.getAnnotatedLabels(B.class);
    assertThat(labels).contains("A");
    assertThat(labels).contains("B");
}

public static List<String> getAnnotatedLabels(Class<?> labeledClass)
{
    var labels = new ArrayList<String>();

    do
    {
        labels.addAll(Arrays.asList(labeledClass.getAnnotationsByType(Label.class))
            .stream()
            .map(labelAnnotations -> labelAnnotations.value())
            .toList());

        labeledClass = labeledClass.getSuperclass();
    } while (labeledClass != Object.class);

    return labels;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接