Java Type Annotations #2: Constructors

In the Java programming language, we can annotate constructor declarations. For example, it is common for dependency injection (DI) libraries to allow the following:
@InjectMyService(FooProcessor processor, BarLogger logger) { this.processor = processor; this.logger = logger;}
In the code above, MyService
is the constructor of a hypothetical service,
and it is annotated with an Inject
annotation.
The latter indicates to the DI library that it should use the annotated constructor to create the instance of our service.
Constructors and Declaration Annotations
In this blog post, we are not interested in DI libraries.
It is just that the Inject
annotation is allowed at the constructor declaration.
If we were to look at the annotation declaration itself, we would probably find something close to the following:
@Target({METHOD, CONSTRUCTOR, FIELD})@Retention(RUNTIME)public @interface Inject {}
Notice that, among the kind of elements that the annotation is allowed to target, we find CONSTRUCTOR
among them.
As the Inject
annotation targets Java declarations exclusively, when we use it we say that it is a declaration annotation.
Constructors and Type Annotations
Up until recently, I had no idea that Java constructors could also be annotated with type annotations. I used to look at constructors all the time, and still miss the fact that an use of type was right there.
To illustrate, let's declare the following type annotation:
@Target(ElementType.TYPE_USE)@Retention(RetentionPolicy.RUNTIME)@interface T {}
It is an annotation named T
which targets types only.
Additionally, later in this post, we'll try to access it at runtime, so we've marked it with the RUNTIME
retention policy.
Next, let's use it to annotate the constructor of the following Java class:
static class Subject { public @T Subject() {} }
The code above compiles without errors.
Please note that the Subject
class is a nested class and, as we don't want it to be an inner class, we've used the static
modifier.
Inspecting the Annotation at Runtime
For completeness, let's write a program that lists the type annotations on the constructor declared earlier:
void main() throws NoSuchMethodException { final Class<?> type; type = Subject.class; final Constructor<?> constructor; // 1 constructor = type.getDeclaredConstructor(); final AnnotatedType annotatedType; // 2 annotatedType = constructor.getAnnotatedReturnType(); final Annotation[] typeAnnotations; // 3 typeAnnotations = annotatedType.getDeclaredAnnotations(); print("Type annotations are:", typeAnnotations); // 4}private void print(String msg, Annotation[] annotations) { System.out.println(msg); for (Annotation annotation : annotations) { System.out.println(annotation); }}
In the main
method:
-
From the
Subject
class object, we obtain theConstructor
object representing the constructor. -
From the constructor object, we obtain a reference to the
AnnotatedType
object representing the type of the constructed object. -
From the
AnnotatedType
, we retrieve all annotations directly present on it. -
We iterate over these annotations and print their names.
When executed, the program produces the following output:
Type annotations are:
@TypeAnnotations2.T()
So, in fact, our constructor is annotated with the type annotation T
.
Conclusion
In the Java programming language, constructor bodies must not contain return
statements with a value expression.
However, similarly to a method that returns, say, a Foo
object, constructors also produces a value.
The result of invoking a constructor, assuming it completes normally, is a new object instance,
whose type is the type of the class where the constructor is declared.
Therefore, in a constructor declaration, there's a use of a type: the type of the object being constructed. And, in Java, we can annotate any use of a type. And annotations on a use of a type are called type annotations.
You can find the source code used in this blog post in this Gist.