Annotations
provide a way to associate arbitrary information or
metadata
with program elements.
Syntactically, annotations are used
like modifiers and can be applied to the declarations of packages,
types, constructors, methods, fields, parameters, and local
variables. The information stored in an annotation takes the form of
name
=
value
pairs, whose type is specified by the annotation
type. The annotation type is a kind of interface that also
serves to provide access to the annotation through the Java
Reflection API.
Annotations can be used to associate any kind of information you want
with a program element. The only fundamental rule is that an
annotation cannot affect the way the program runs: the code must run
identically even if you add or remove annotations. Another way to say
this is that the
Java interpreter ignores annotations
(although it does make
“runtime-visible” annotations
available for reflective access through the Java Reflection API).
Since the Java VM ignores annotations, an annotation type is not
useful unless accompanied by a tool that can do something with the
information stored in annotations of that type. In this chapter
we’ll cover standard annotation and meta-annotation
types like Override
and Target
.
The tool that accompanies these types is the Java compiler, which
must process them in certain ways (as we’ll describe
later in this section).
It is easy to imagine any number of other uses for
annotations.[7] A local variable might be annotated with a type named
NonNull, as an assertion that the variable would never have a
null
value. An associated (hypothetical)
code-analysis tool could then parse the code and attempt to verify
the assertion. The JDK includes a tool named apt
(for Annotation Processing Tool) that provides a framework for
annotation processing tools: it scans source code for annotations and
invokes specially written annotation processor classes that you
provide. See Chapter 8 for more on
apt. Annotations will probably find their widest
use in enterprise programming where they may replace tools such as
XDoclet, which processes metadata embedded in
ad-hoc javadoc comments.
This section begins with an introduction to annotation-related terminology. We then cover the standard annotation types introduced in Java 5.0, annotations supported by javac that you can use in your programs right away. Next, we describe the syntax for writing arbitrary annotations and briefly cover the use of the Java Reflection API for querying annotations at runtime. At this point, we move on to more esoteric material on defining new annotation types, a task that few programmers will ever need to do. This final part of the chapter also discusses meta-annotations.
The key concept to understand about annotations is that an annotation simply associates information or metadata with a program element. Annotations never affect the way a Java program runs, but they may affect things like compiler warnings or the behavior of auxiliary tools such as documentation generators, stub generators, and so forth.
The following terms are used frequently when discussing annotations. Of particular importance is the distinction between annotation and annotation type.
- annotation
An annotation associates arbitrary information or metadata with a Java program element. Annotations use new syntax introduced in Java 5.0 and behave like modifiers such as
public
orfinal
. Each annotation has a name and zero or more members. Each member has a name and a value, and it is thesename
=
value
pairs that carry the annotation’s information.- annotation type
The name of an annotation as well as the names, types, and default values of its members are defined by the annotation type . An annotation type is essentially a Java interface with some restrictions on its members and some new syntax used in its declaration. When you query an annotation using the Java Reflection API, the returned value is an object that implements the annotation type interface and allows individual annotation members to be queried. Java 5.0 includes three standard annotation types in the
java.lang
package. We’ll see these annotations in Section 4.3.2 later in this chapter.- annotation member
The members of an annotation are declared in an annotation type as no-argument methods. The method name and return type define the name and type of the member. A special
default
syntax allows the declaration of a default value for any annotation member. An annotation appearing on a program element includesname
=
value
pairs that define values for all annotation members that do not have default values and may also include values that override the defaults of other members.- marker annotation
An annotation type that defines no members is called a marker annotation . An annotation of this type carries information simply by its presence or absence.
- meta-annotation
A meta-annotation is an annotation applied to the declaration of an annotation type. Java 5.0 includes several standard meta-annotation types in the
java.lang.annotation
package. They are used to specify things like which program elements the annotation can be applied to.- target
The target of an annotation is the program element that is annotated. Annotations can be applied to packages, types (classes, interfaces, enumerated types, and even annotation types), type members (methods, constructors, fields, and enumerated values), method parameters, and local variables (including loop variables and
catch
parameters). The declaration of an annotation type may include a meta-annotation that restricts the allowable targets for that type of annotation.- retention
The retention of an annotation specifies how long the information contained in the annotation is retained. Some annotations are discarded by the compiler and appear only in source code. Others are compiled into the class file. Of those that are compiled into the class file, some are ignored by the virtual machine, and others are read by the virtual machine when the class that contains them is loaded. The declaration of an annotation type can use a meta-annotation to specify the retention for annotations of that type. Annotations that are loaded by the VM are runtime-visible and can be queried by the reflective APIs of
java.lang.reflect
.- metadata
When discussing annotations, the term metadata commonly refers to the information carried by an annotation or to the annotation itself. Because this term is used in many different ways in computer programming literature, I have avoided using it in this chapter.
Java 5.0 defines three standard annotation types in the
java.lang
package. The following sections describe these annotation types and
explain how to use them to annotate your code.
java.lang.Override
is a
marker annotation type that can be used to annotate methods but no
other program element. An annotation of this type serves as an
assertion that the annotated method overrides a method of a
superclass. If you use this annotation on a method that does not
override a superclass method, the compiler issues a compilation error
to alert you to this fact.
This annotation is intended to address a common category of programming errors that result when you attempt to override a superclass method but get the method name or signature wrong. In this case, you may have overloaded the method name but not actually overridden the method, and your code never gets invoked.
To use this annotation type, simply include
@Override
in the modifiers of the desired method. By convention,
@Override
comes before other modifiers. Also by
convention, there is no space between the @
character and the name Override
, even though it is
technically allowed. Note that because the
java.lang
package is always automatically
imported, you never need to include the package name to use this
annotation type. Here is an example in which the
@Override
annotation is used on a method that
fails to correctly override the toString()
method
of its superclass.
@Override public String toSting() { // Oops. Note the misspelling here! // Simply put square brackets around our superclass's output return "[" + super.toString() + "]"; }
Without the annotation, the typo might go unnoticed and
we’d have a puzzling bug: why isn’t
the toString( )
method working correctly? But with
the annotation, the compiler gives us the answer: the
toString()
method does not work as expected
because it is not actually overridden.
Note that the @Override
annotation applies only to
methods that are intended to override a superclass method and not to
methods that are intended to implement a method defined in an
interface. The compiler already produces an error if you fail to
correctly implement an interface method.
java.lang.Deprecated
is a
marker
annotation that is similar to the
@deprecated
javadoc tag. (See Chapter 7 for details on
writing Java documentation comments.) If you annotate a type or type
member with @Deprecated
, it tells the compiler
that use of the annotated element is discouraged. If you use (or
extend or override) a deprecated type or member from code that is not
itself declared @Deprecated
, the compiler issues a
warning.
Note that the @Deprecated
annotation type does not
deprecate the @deprecated
javadoc tag. The
@Deprecated
annotation is intended for the Java
compiler. The javadoc tag, on the other hand, is intended for the
javadoc tool and serves as documentation: it may
include a description of why the program element has been deprecated
and what it has been superseded by or replaced with.
In Java 5.0, the compiler continues to look for
@deprecated
javadoc tags and uses them to generate
warnings as it always has. This behavior may be phased out, however,
and you should begin to use the @Deprecated
annotation in addition to the @deprecated
javadoc
tag.
Here is an example that uses both the annotation and the javadoc tag:
/** * The Sony Betamax video cassette format. * @deprecated No one has players for this format any more. Use VHS instead. */ @Deprecated public class Betamax { ... }
The
@SuppressWarnings
annotation is used to selectively turn off compiler
warnings for classes,
methods, or field and variable initializers.[8] In
Java 5.0, Sun’s
javac
compiler has a powerful -Xlint
option that causes
it to issue warnings about “lint”
in your program—code that is legal but is likely to represent a
programming error. These warnings include the
“unchecked warning” that appears
when you use a generic collection class without specifying a value
for its type parameters, for example, or the warning that appears if
a case
in a switch
statement
does not end with a break
,
return
, or throw
and allows
control to “fall through” to the
next case.
Typically, when you see one of these lint warnings from the compiler,
you should investigate the code that caused it. If it truly
represents an error, you then correct it. If it simply represents
sloppy programming, you may be able to rewrite your code so that the
warning is no longer necessary. For example, if the warning tells you
that you have not covered all possible cases in a
switch
statement on an enumerated type, you can
avoid the warning by adding a defensive default
case to the switch
statement, even if you are sure
that it will never be invoked.
On the other hand, sometimes there is nothing you can do to avoid the
error. For example, if you use a generic collection class in code
that must interact with nongeneric legacy code, you cannot avoid an
unchecked warning. This is where @SuppressWarnings
comes in: add this annotation to the nearest relevant set of
modifiers (typically on method modifiers) to tell the compiler that
you’re aware of the issue and that it should stop
pestering you about it.
Unlike Override
and Deprecated
,
SuppressWarnings
is not a marker annotation. It
has a single member named value
whose type is
String[ ]
. The value of this member is the names
of the warnings to be suppressed. The
SuppressWarnings
annotation does not define what
warning names are allowed: this is an issue for compiler
implementors. For the javac compiler, the
warning names accepted by the -Xlint
option are
also legal for the @SuppressWarnings
annotation.
It is legal to specify any warning names you want: compilers ignore
(but may warn about) warning names they do not recognize.
So, to suppress warnings named unchecked
and
fallthrough
, you could use an annotation that
looks like the following. Annotation syntax follows the name of the
annotation type with a parenthesized, comma-separated list of
name
=
value
pairs. In this case, the SuppressWarnings
annotation type defines only a single member, so there is only a
single pair within parentheses. Since the member value is an array,
curly braces are used to delimit array elements:
@SuppressWarnings(value={"unchecked","fallthrough"}) public void lintTrap() { /* sloppy method body omitted */ }
We can abbreviate this annotation somewhat. When an annotation has a single member and that member is named “value”, you are allowed (and encouraged) to omit the “value=” in the annotation. So the annotation above should be rewritten as:
@SuppressWarnings({"unchecked","fallthrough"})
Hopefully you will not often have more than one unresolvable lint warning in any particular method and will need to suppress only a single named warning. In this case, another annotation abbreviation is possible. When writing an array value that contains only a single member, you are allowed to omit the curly braces. In this case we might have an annotation like this:
@SuppressWarnings("unchecked")
In the descriptions of the standard annotation types, we’ve seen the syntax for writing marker annotations and the syntax for writing single-member annotations, including the shortcut allowed when the single member is named “value” and the shortcut allowed when an array-typed member has only a single array element. This section describes the complete syntax for writing annotations.
An annotation consists of the @
character followed
by the name of the annotation type (which may include a package name)
followed by a parenthesized, comma-separated list of
name
=
value
pairs for each of the members defined by the annotation type. Members
may appear in any order and may be omitted if the annotation type
defines a default value for that member. Each
value
must be a literal or compile-time
constant, a nested annotation, or an array.
Near the end of this chapter, we define an annotation type named
Reviews
that has a single member that is an array
of @Review
annotations. The
Review
annotation type has three members:
“reviewer” is a
String
,
“comment” is an optional
String
with a default value, and
“grade” is a value of the nested
enumerated type Review.Grade
. Assuming that the
Reviews
and Review
types are
properly imported, an annotation using these types might look like
this (note the use of nested annotations, enumerated types, and
arrays in this annotation):
@Reviews({ // Single-value annotation, so "value=" is omitted here @Review(grade=Review.Grade.EXCELLENT, reviewer="df"), @Review(grade=Review.Grade.UNSATISFACTORY, reviewer="eg", comment="This method needs an @Override annotation") })
Another important rule of annotation syntax is that no program
element may have more than one instance of the same annotation. It is
not legal, for example, to simply place multiple
@Review
annotations on a class. This is why the
@Reviews
annotation is defined to allow an array
of @Review
annotations.
The
values of
annotation
members must be non-null
compile-time constant
expressions that are assignment-compatible with the declared type of
the member. Allowed member types are the primitive types,
String
, Class
, enumerated
types, annotation types, and arrays of any of the above types (but
not an array of arrays). For example, the expressions
2*Math.PI
and "hello"+"world
"
are legal values for members of type double
and
String
, respectively.
Near the end of the chapter, we define an annotation type named
UncheckedExceptions
whose sole member is an array
of classes that extend RuntimeException
. An
annotation of this type might look like this:
@UncheckedExceptions({ IllegalArgumentException.class, StringIndexOutOfBoundsException.class })
Annotations are most commonly placed on type definitions (such as classes) and their members (such as methods and fields). Annotations may also appear on packages, parameters, and local variables. This section provides more information about these less common annotation targets.
A package annotation appears before
the package
declaration in a file named
package-info.java. This file should not contain
any type declarations
(“package-info” is not a legal Java
identifier, so it cannot contain any public type definitions).
Instead, it should contain an optional javadoc comment, zero or more
annotations, and a package
declaration. For
example:
/** * This package holds my custom annotation types. */ @com.davidflanagan.annotations.Author("David Flanagan") package com.davidflanagan.annotations;
When the package-info.java file is compiled, it
produces a class file named package-info.class
that contains a synthetic interface declaration. This interface has
no members, and its name, package-info
, is not a
legal Java identifier, so it cannot be used in Java source code. It
exists simply as a placeholder for package annotations with class or
runtime retention.
Note that package annotations appear outside the scope of any
package
or import
declaration.
This means that package annotations should always include the package
name of the annotation type (unless the package is
java.lang
).
Annotations on method parameters, catch clause parameters, and local variables simply appear as part of the modifier list for those program elements. The Java class file format has no provision for storing annotations on local variables or catch clause parameters, so those annotations always have source retention. Method parameter annotations can be retained in the class file, however, and may have class or runtime retention.
Finally, note that the syntax for enumerated type definitions does not allow any modifiers to be specified for enumerated values. It does, however, allow annotations on any of the values.
Annotations must include a value for every member that does not have a default value defined by the annotation type. Annotations may, of course, include values for other members as well.
There is one important detail to understand about how default values are handled. Default values are stored in the class file of the annotation type and are not compiled into annotations themselves. If you modify an annotation type so that the default value of one of its members changes, that change affects all annotations of that type that do not specify an explicit value for that member. Already-compiled annotations are affected, even if they are never recompiled after the change to the type.
The
Reflection API of
java.lang.reflect
has been extended in Java 5.0 to
support reading of runtime-visible annotations. (Remember that an
annotation is only visible at runtime if its annotation type is
specified to have runtime retention, that is, if the annotation is
both stored in the class file and read by the Java VM when the class
file is loaded.) This section briefly covers the new reflective
capabilities. For full details, look up the interface
java.lang.reflect.AnnotatedElement
in the reference section.
AnnotatedElement
represents a program element that
can be queried for annotations. It is implemented by
java.lang.Package
,
java.lang.Class
, and indirectly implemented by the
Method
, Constructor
, and
Field
classes of
java.lang.reflect
. Annotations on method
parameters can be queried with the getParameterAnnotations(
)
method of the Method
or
Constructor
class.
The following code uses the isAnnotationPresent(
)
method of
AnnotatedElement
to determine whether a method is
unstable by checking for an @Unstable
annotation.
It assumes that the Unstable
annotation type,
which we’ll define later in the chapter, has runtime
retention. Note that this code uses class literals to specify both
the class to be checked and the annotation to check for:
import java.lang.reflect.*; Class c = WhizzBangClass.class; Method m = c.getMethod("whizzy", int.class, int.class); boolean unstable = m.isAnnotationPresent(Unstable.class);
isAnnotationPresent()
is useful for
marker annotations. When working with
annotations that have members, though, we typically want to know the
value of those members. For this, we use the
getAnnotation()
method. And here we see the beauty of the
Java annotation system: if the specified annotation exists, the
object returned by this method implements the annotation type
interface, and you can query the value of any member simply by
invoking the annotation type method that defines that member.
Consider the @Reviews
annotation that appeared
earlier in the chapter, for example. If the annotation type was
declared with runtime retention, you could query it as follows:
AnnotatedElement target = WhizzBangClass.class; // the type to query // Ask for the @Reviews annotation as an object that implements Reviews Reviews annotation = target.getAnnotation(Reviews.class); // Reviews has a single member named "value" that is an array of reviews Review[] reviews = annotation.value(); // Loop through the reviews for(Review r : reviews) { Review.Grade grade = r.grade(); String reviewer = r.reviewer(); String comment = r.comment(); System.out.printf("%s assigned a grade of %s and comment '%s'%n", reviewer, grade, comment); }
Note that these reflective methods correctly resolve default annotation values for you. If an annotation does not include a value for a member with a default value, the default value is looked up within the annotation type itself.
An annotation type is an interface, but it is not a normal one. An annotation type differs from a normal interface in the following ways:
An annotation type is defined with the keyword
@interface
rather than withinterface
. An@interface
declaration implicitly extends the interfacejava.lang.annotation.Annotation
and may not have an explicitextends
clause of its own.The methods of an annotation type must be declared with no arguments and may not throw exceptions. These methods define annotation members: the method name becomes the member name, and the method return type becomes the member type.
The return value of annotation methods may be a primitive type, a
String
, aClass
, an enumerated type, another annotation type, or a single-dimensional array of one of those types.Any method of an annotation type may be followed by the keyword
default
and a value compatible with the return type of the method. This strange new syntax specifies the default value of the annotation member that corresponds to the method. The syntax for default values is the same as the syntax used to specify member values when writing an annotation.null
is never a legal default value.Annotation types and their methods may not have type parameters—annotation types and members cannot be made generic. The only valid use of generics in annotation types is for methods whose return type is
Class
. These methods may use a bounded wildcard to specify a constraint on the returned class.
In other ways, annotation types declared with
@interface
are just like regular interfaces. They
may include constant definitions and static member types such as
enumerated type definitions. Annotation types may also be implemented
or extended just as normal interfaces are. (The classes and
interfaces that result from doing this are not themselves annotation
types, however: annotation types can be created only with an
@interface
declaration.)
We now define the annotation types used in our examples. These
examples illustrate the syntax of annotation type declarations and
demonstrate many of the differences between
@interface
and interface
. We
start with the simple marker annotation type
Unstable
. Because we used this type earlier in the
chapter in a reflection example, its definition includes a
meta-annotation that gives it runtime retention and makes it
accessible to the reflection API. Meta-annotations are covered below.
package com.davidflanagan.annotations; import java.lang.annotation.*; /** * Specifies that the annotated element is unstable and its API is * subject to change. */ @Retention(RetentionPolicy.RUNTIME) public @interface Unstable {}
The next annotation type defines a single member. By naming the
member value
, we enable a syntactic shortcut for
anyone using the annotation:
/** * Specifies the author of a program element. */ public @interface Author { /** Return the name of the author */ String value(); }
The next example is more complex. The Reviews
annotation type has a single member, but the type of the member is
complex: it is an array of Review
annotations. The
Review
annotation type has three members, one of
which has an enumerated type defined as a member of the
Review
type itself, and another of which has a
default value. Because the Reviews
annotation type
is used in a reflection example, we’ve given it
runtime retention with a
meta-annotation:
import java.lang.annotation.*; /** * An annotation of this type specifies the results of one or more * code reviews for the annotated element */ @Retention(RetentionPolicy.RUNTIME) public @interface Reviews { Review[] value(); } /** * An annotation of this type represents a single code review of the * annotated element. Every review must specify the name of the reviewer * and the grade assigned to the code. Optionally, reviews may also include * a comment string. */ public @interface Review { // Nested enumerated type public static enum Grade { EXCELLENT, SATISFACTORY, UNSATISFACTORY }; // These methods define the annotation members Grade grade(); // member named "grade" with type Grade String reviewer(); String comment() default ""; // Note default value here. }
Finally, suppose we wanted to annotate methods to list the unchecked
exceptions (but not errors) that they might throw. Our annotation
type would have a single member of array type. Each element of the
array would be the Class
of an exception. In order
to enforce the requirement that only unchecked exceptions are used,
we use a bounded wildcard on Class
:
public @interface UncheckedExceptions { Class<? extends RuntimeException>[] value(); }
Annotation
types can themselves be annotated. Java 5.0 defines four standard
meta-annotation types that provide information
about the use and meaning of other annotation types. These types and
their supporting classes are in the
java.lang.annotation
package, and you can find
complete details in the quick-reference section of the book.
The Target
meta-annotation type specifies the
“targets” for an annotation type.
That is, it specifies which program elements may have annotations of
that type. If an annotation type does not have a
Target
meta-annotation, it can be used with any of
the program elements described earlier. Some annotation types,
however, make sense only when applied to certain program elements.
Override
is one example: it is only meaningful
when applied to a method. An @Target
meta-annotation applied to the declaration of the
Override
type makes this explicit and allows the
compiler to reject an @Override
when it appears in
an inappropriate context.
The Target
meta-annotation type has a single
member named value
. The type of this member is
java.lang.annotation.ElementType[]
.
ElementType
is an enumerated type whose enumerated
values represent program elements that can be annotated.
We discussed annotation
retention
earlier in the chapter. It specifies
whether an annotation is discarded by the compiler or retained in the
class file, and, if it is retained in the class file, whether it is
read by the VM when the class file is loaded. By default, annotations
are stored in the class file but not available for runtime reflective
access. The three possible retention values (source, class, and
runtime) are described by the enumerated type
java.lang.annotation.RetentionPolicy
.
The Retention
meta-annotation type has a single
member named value
whose type is
RetentionPolicy
.
Documented
is a meta-annotation type used to
specify that annotations of some other type should be considered part
of the public API of the annotated program element and should
therefore be documented by tools like javadoc.
Documented
is a marker annotation: it has no
members.
The @Inherited
meta-annotation is a marker
annotation that specifies that the annotated type is an inherited
one. That is, if an annotation type @Inherited
is
used to annotate a class, the annotation applies to subclasses of
that class as well.
Note that @Inherited
annotation types are
inherited only by subclasses of an annotated class. Classes do not
inherit annotations from interfaces they implement, and methods do
not inherit annotations from methods they override.
The Reflection API enforces the
inheritance if the @Inherited
annotation type is
also annotated
@Retention(RetentionPolicy.RUNTIME)
. If you use
java.lang.reflect
to query a class for an
annotation of an @Inherited
type, the reflection
code checks the specified class and each of its ancestors until an
annotation of the specified type is found or the top of the class
hierarchy is reached.
[7] We won’t have to imagine these uses for long. At the time of this writing, JSR 250 is making its way through the Java Community Process to define a standard set of common annotations for J2SE and J2EE.
[8] The
javac compiler did not yet support the
@SuppressWarnings
annotation when this chapter was
written. Full support is expected in Java 5.1.
Get Java in a Nutshell, 5th Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.