Reputation: 8718
In Java annotations marked as @Inherited
will only work when annotating classes:
Note that this meta-annotation type has no effect if the annotated type is used to annotate anything other than a class. Note also that this meta-annotation only causes annotations to be inherited from superclasses; annotations on implemented interfaces have no effect.
So interfaces or methods annotated with an @Inherited
annotation will not result in implementing classes/methods to also be annotated with the annotation. The reason for this is most likely, that the compiler would'n know which of the annotations to choose, if there are multiple annotations in the class hierarchy as described here.
Now Java 8 introduced the new annotation @Repeatable
. I think it would have been natural to remove the above restrictions for annotations that are both marked as @Inherited
and @Repeatable
, because the compiler should then be able to add the conflicting annotations to the @Repeatable
annotation.
Given the following example:
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());
}
}
}
I would have hoped the output to be ABCD
but it is "just" CD
(i.e. @Inherited
is working exactly like pre Java 8).
Does anyone know if there was good reason for not removing the @Inherited
restrictions regarding interfaces and methods in the case of @Repeatable
annotations for Java 8?
Is there any workaround to achieve the ABCD
output for the above type hierarchy? (other than using reflection to scan the super interfaces for annotations...)
Upvotes: 3
Views: 2361
Reputation: 11
but I found this thread searching for a similar solution. In the end I wrote this helper method:
@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;
}
Upvotes: 0
Reputation: 298539
Please recall the documentation of @Inherited
:
If an Inherited meta-annotation is present on an annotation type declaration, and the user queries the annotation type on a class declaration, and the class declaration has no annotation for this type, then the class's superclass will automatically be queried for the annotation type.
In other words, @Inherited
never was intended to be a feature for collecting multiple annotations on a type hierarchy. Instead, you will get the annotation of the most specific type which has an explicit annotation.
In other words, if you change your declaration to
@RepeatableAnnotation("FOO") @RepeatableAnnotation("BAR") class Base {}
@RepeatableAnnotation("C") @RepeatableAnnotation("D")
public class TestClass extends Base implements IntefaceA, IntefaceB {
it won’t change the result; FOO
and BAR
of Base
are not inherited by TestClass
as it has the explicit annotation values C
and D
.
Expanding this to the interface
hierarchy would be awkward due to the multiple inheritance and the fact that a super-interface may turn out to be a sub-interface of another super-interface so finding the most specific one is not trivial. This differs heavily from the linear search of the superclass hierarchy.
You may encounter the situation where multiple unrelated annotated interface
s exist but it’s not clear why this ambiguity should be resolved by joining them into one repeated annotation. This would not harmonize with the behavior in all other scenarios.
Note that the answer you have linked is a bit odd as it shows code using a method annotation but method annotations are never inherited, regardless of whether you specified @Inherited
or not (an audit tool should generate a warning when you combine @Target(ElementType.METHOD)
with @Inherited
, imho). @Inherited
is relevant for type annotations only.
Upvotes: 4