In the following sections, we cover everything you need to know about the annotation syntax.
1. Annotation Interfaces
An annotation is defined by an annotation interface:
modifiers @interface AnnotationName
{
elementDeclaration1
elementDeclaration2
…
}
Each element declaration has the form
type elementName();
or
type elementName() default value;
For example, the following annotation has two elements, assignedTo and severity:
public @interface BugReport
{
String assignedTo() default “[none]”;
int severity();
}
All annotation interfaces implicitly extend the java.lang.annotation.Annotation interface. That interface is a regular interface, not an annotation interface. See the API notes at the end of this section for the methods provided by this interface. You cannot extend an annotation interface—in other words, all annotation interfaces directly extend java.lang.annotation.Annotation. You never supply classes that implement annotation interfaces.
The methods of an annotation interface have no parameters and no throws clauses. They cannot be default or static methods, and they cannot have type parameters.
The type of an annotation element is one of the following:
- A primitive type (int, short, long, byte, char, double, float, or boolean)
- String
- Class (with an optional type parameter such as Class<? extends MyClass>)
- An enum type
- An annotation type
- An array of the preceding types (an array of arrays is not a legal element type)
Here are examples of valid element declarations:
public @interface BugReport
{
enum Status { UNCONFIRMED, CONFIRMED, FIXED, NOTABUG };
boolean showStopper() default false;
String assignedTo() default “[none]”;
Class<?> testCase() default Void.class;
Status status() default Status.UNCONFIRMED;
Reference ref() default @Reference(); // an annotation type String[] reportedBy();
}
2. Annotations
Each annotation has the format
@AnnotationName(elementNamei=valuei, elementNamej=value 2, . . .)
For example,
@BugReport(assignedTo=”Harry”, severity=10)
The order of the elements does not matter. The annotation @BugReport(severity=10, assignedTo=”Harry”) is identical to the preceding one.
The default value of the declaration is used if an element value is not specified. For example, consider the annotation
@BugReport(severity=10)
The value of the assignedTo element is the string “[none]”.
Two special shortcuts can simplify annotations.
If no elements are specified, either because the annotation doesn’t have any or because all of them use the default value, you don’t need to use parentheses. For example,
@BugReport
is the same as
@BugReport(assignedTo=”[none]”, severity=0)
Such an annotation is called a marker annotation.
The other shortcut is the single-value annotation. If an element has the special name value and no other element is specified, you can omit the element name and the = symbol. For example, had we defined the ActionListenerFor annotation interface of the preceding section as
public @interface ActionListenerFor
{
String value();
}
then the annotations could be written as
@ActionListenerFor(“yellowButton”) instead
of
@ActionListenerFor(value=”yellowButton”)
An item can have multiple annotations:
@Test
@BugReport(showStopper=true, reportedBy=”Joe”)
public void checkRandomInsertions()
If the author of an annotation declared it to be repeatable, you can repeat the same annotation multiple times:
@BugReport(showStopper=true, reportedBy=”Joe”)
@BugReport(reportedBy={“Harry”, “Carl”})
public void checkRandomInsertions()
If an element value is an array, enclose its values in braces:
@BugReport(. . ., reportedBy={“Harry”, “Carl”})
You can omit the braces if the element has a single value:
@BugReport(. . ., reportedBy=”Joe”) // OK, same as {“Joe”}
Since an annotation element can be another annotation, you can build arbitrarily complex annotations. For example,
@BugReport(ref=@Reference(id=”3352627″), . . .)
NOTE: It is an error to introduce circular dependencies in annotations. For example, BugReport has an element of the annotation type Reference, therefore Reference cannot have an element of type BugReport.
3. Annotating Declarations
There are many places where annotations can occur. They fall into two categories: declarations and type uses. Declaration annotations can appear at the declarations of
- Packages
- Classes (including enum)
- Interfaces (including annotation interfaces)
- Methods
- Constructors
- Instance fields (including enum constants)
- Local variables
- Parameter variables
- Type parameters
For classes and interfaces, put the annotations before the class or interface keyword:
@Entity public class User { . . . }
For variables, put them before the type:
@SuppressWarnings(“unchecked”) List<User> users = . . .;
public User getUser(@Param(“id”) String userId)
A type parameter in a generic class or method can be annotated like this:
public class Cache<@Immutable V> { . . . }
A package is annotated in a file package-info.java that contains only the package statement preceded by annotations.
/**
Package-level Javadoc
*/
@GPL(version=”3″)
package com.horstmann.corejava;
import org.gnu.GPL;
4. Annotating Type Uses
A declaration annotation provides some information about the item being declared. For example, in the declaration
public User getUser(@NonNull String userid)
it is asserted that the userid parameter is not null.
Now, suppose we have a parameter of type List<String>, and we want to express that all of the strings are non-null. That is where type use annotations come in. Place the annotation before the type argument: List<@NonNull String>.
Type use annotations can appear in the following places:
- With generic type arguments: List<@NonNull String>, Comparator.<@NonNull String> reverseOrder().
- In any position of an array: @NonNull String[][] words (words[i][j] is not null), String @NonNull [][] words (words is not null), String[] @NonNull [] words (words[i] is not null).
- With superclasses and implemented interfaces: class Warning extends @Localized Message.
- With constructor invocations: new @Localized String(. . .).
- With casts and instanceof checks: (@Localized String) text, if (text instanceof @Localized String). (The annotations are only for use by external tools. They have no effect on the behavior of a cast or an instanceof check.)
- With exception specifications: public String read() throws @Localized IOException.
- With wildcards and type bounds: List<@Localized ? extends Message>, List<? extends @Localized Message>.
- With method and constructor references: @Localized Message::getText.
There are a few type positions that cannot be annotated:
@NonNutt String.class // ERROR: Cannot annotate class literal
import java.lang.@NonNull String; // ERROR: Cannot annotate import
You can place annotations before or after other modifiers such as private and static. It is customary (but not required) to put type use annotations after other modifiers, and declaration annotations before other modifiers. For example,
private @NonNull String text; // Annotates the type use
@Id private String userId; // Annotates the variable
5. Annotating this
Suppose you want to annotate parameters that are not being mutated by a method.
public class Point
{
public boolean equals(@ReadOnly Object other) { . . . }
}
Then a tool that processes this annotation would, upon seeing a call p.equals(q)
reason that q has not been changed.
But what about p?
When the method is called, the this variable is bound to p. But this is never declared, so you cannot annotate it.
Actually, you can declare it, with a rarely used syntax variant, just so that you can add an annotation:
public class Point
{
public boolean equals(@ReadOnly Point this, @ReadOnly Object other) { . . . }
}
The first parameter is called the receiver parameter. It must be named this. Its type is the class that is being constructed.
A different hidden parameter is passed to the constructor of an inner class, namely the reference to the enclosing class object. You can make that parameter explicit as well:
public class Sequence
{
private int from; private int to;
class Iterator implements java.util.Iterator<Integer>
{
private int current;
public Iterator(@ReadOnly Sequence Sequence.this)
{
this.current = Sequence.this.from;
}
}
}
The parameter must be named just like when you refer to it, EnclosingClass .this, and its type is the enclosing class.
Source: Horstmann Cay S. (2019), Core Java. Volume II – Advanced Features, Pearson; 11th edition.