Java Type Annotations #6: Receiver Parameter

In the Java Programming language, type annotations are those which annotates the use of a type. When we invoke an object's method, we use at least one type: the type of the object on which the method is invoked.
From the two statements above, we conclude that we can annotate this use of a type with a type annotation. For this purpose, the language provides the receiver parameter.
Example
First, let's declare a type annotation X
.
@Target(ElementType.TYPE_USE)@Retention(RetentionPolicy.RUNTIME)@interface X {}
Our annotation targets uses of a type and its value is retained at runtime. We'll use it in the following manner:
static class Receiver { @Override public String toString(@X Receiver this) { return "Receiver Parameter"; }}
We have declared a Receiver
class, which is a static nested class of the implicitly declared class.
Our class declares a single toString
method.
The method declares a single parameter whose type is Receiver
and whose name is the reserved keyword this
.
Finally, the type of the method parameter is annotated with the X
annotation defined earlier.
Our Receiver
class compiles without errors.
The parameter of the toString
method is formally called the receiver parameter.
It is not a formal parameter and its single purpose is to allow the type of the object on which a method is invoked to be annotated.
Verifying the Annotation at Runtime
Let's write a program to inspect the receiver parameter we've just declared.
We will try to confirm that it is annotated with the X
annotation.
Additionally, we want to confirm that the method we've declared overrides the Object::toString
method.
In other words, we want to confirm is not an overload.
We write the following:
void main() throws NoSuchMethodException { Class<?> type; type = Receiver.class; // 1 Method method; method = type.getDeclaredMethod("toString"); // 2 AnnotatedType recType; recType = method.getAnnotatedReceiverType(); // 3 System.out.println("Annotations on receiver:"); for (Annotation annotation : recType.getDeclaredAnnotations()) { System.out.println(annotation); // 4 } System.out.println(new Receiver()); // 5}
In our main
method:
-
We obtain the
Class
object of ourReceiver
class. -
From the
Class
object, we obtain theMethod
object representing thetoString
method. -
Using the
Method
object, we obtain the annotated type of the receiver parameter. -
We iterate over the annotations on the receiver parameter's type and print their names.
-
We indirectly invoke the
toString
method of a new instance of theReceiver
class.
When we execute this program, it produces the following output:
Annotations on receiver:
@TypeAnnotations6.X()
Receiver Parameter
We observe that:
-
The type of the receiver parameter is annotated with the
X
annotation. -
The
toString
we've declared overrides theObject::toString
method.
In other words, we've annotated the type of the receiver of the toString
method call.
Inner Classes
As a final note, we can declare a receiver parameter on a constructor, provided it belongs to an inner class:
static class Outer { class Inner { Inner(@X Outer Outer.this) {} }}
The receiver parameter, in this case, represents the enclosing instance to which the newly constructed instance is associated.
Conclusion
When we invoke a method on an object we are using at least one type: the type of the object on which the method is invoked. We can annotate this use of a type by declaring a receiver parameter on the method.
It is worth noting that the receiver parameter is not a formal parameter. It is just a syntactic construct to allow the use of type annotations. You can read more about the receiver parameter in this blog post.
Finally, you can find the source code used in this blog post in this Gist.