Home > Articles > Programming > Java

  • Print
  • + Share This
This chapter is from the book

10.4. Annotation Syntax

In this section, we cover everything you need to know about the annotation syntax.

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() = 0;
}

Each annotation has the format

@AnnotationName(elementName1=value1, elementName2=value2, . . .)

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")

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 annotation interfaces. In other words, all annotation interfaces directly extend java.lang.annotation.Annotation.

You never supply classes that implement annotation interfaces. Instead, the virtual machine generates proxy classes and objects when needed. For example, when requesting an ActionListenerFor annotation, the virtual machine carries out an operation similar to the following:

return Proxy.newProxyInstance(classLoader, ActionListenerFor.class,
   new
      InvocationHandler()
      {
         public Object invoke(Object proxy, Method m, Object[] args) throws Throwable
         {
            if (m.getName().equals("source")) return value of source annotation;
            . . .
         }
      });

The element declarations in the annotation interface are actually method declarations. The methods of an annotation interface can have no parameters and no throws clauses, and they cannot be generic.

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();
}

Since annotations are evaluated by the compiler, all element values must be compile-time constants. For example,

@BugReport(showStopper=true, assignedTo="Harry", testCase=MyTestCase.class,
   status=BugReport.Status.CONFIRMED, . . .)

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"), . . .)

You can add annotations to the following items:

  • Packages
  • Classes (including enum)
  • Interfaces (including annotation interfaces)
  • Methods
  • Constructors
  • Instance fields (including enum constants)
  • Local variables
  • Parameter variables

However, annotations for local variables can only be processed at the source level. Class files do not describe local variables. Therefore, all local variable annotations are discarded when a class is compiled. Similarly, annotations for packages are not retained beyond the source level.

An item can have multiple annotations, provided they belong to different types. You cannot use the same annotation type more than once when annotating a particular item. For example,

@BugReport(showStopper=true, reportedBy="Joe")
@BugReport(reportedBy={"Harry", "Carl"})
void myMethod()

is a compile-time error. If this is a problem, design an annotation whose value is an array of simpler annotations:

@BugReports({
   @BugReport(showStopper=true, reportedBy="Joe"),
   @BugReport(reportedBy={"Harry", "Carl"})})
void myMethod()
  • + Share This
  • 🔖 Save To Your Account