Annotations
(Metadata)
Since JDK 5, Java has
supported a feature that enables you to embed supplemental information into a
source file. This information, called an annotation,
does not change the actions of a program. Thus, an annotation leaves the semantics
of a program unchanged.
However, this information can
be used by various tools during both development and deployment. For example,
an annotation might be processed by a source-code generator. The term metadata is also used to refer to this
feature, but the term annotation is
the most descriptive and more commonly used.
Annotation
Basics
An annotation is created
through a mechanism based on the interface.
Let’s begin with an example. Here is the declaration for an annotation called MyAnno:
// A simple annotation type.
@interface MyAnno {
String str(); int val();
}
First, notice the @ that
precedes the keyword interface. This
tells the compiler that an annotation type is being declared. Next, notice the
two members str( ) and val( ). All annotations consist solely
of method declarations. However, you don’t provide bodies for these methods.
Instead, Java implements these methods. Moreover, the methods act much like
fields, as you will see.
An annotation cannot include
an extends clause. However, all annotation
types automatically extend the Annotation
interface. Thus, Annotation is a
super-interface of all annotations. It is declared within the java.lang.annotation package. It
overrides hashCode( ), equals( ), and toString( ), which are defined by Object. It also specifies
annotationType( ), which returns
a Class object that represents the
invoking annotation.
Once you have declared an
annotation, you can use it to annotate something. Prior to JDK 8, annotations
could be used only on declarations, and that is where we will begin. (JDK 8
adds the ability to annotate type use, and this is described later in this
chapter. However, the same basic techniques apply to both kinds of
annotations.) Any type of declaration can have an annotation associated with it.
For example, classes, methods, fields, parameters, and enum constants can be annotated. Even an annotation can be
annotated. In all cases, the annotation precedes the rest of the declaration.
When you apply an annotation,
you give values to its members. For example, here is an example of MyAnno being applied to a method
declaration:
// Annotate a method.
@MyAnno(str = "Annotation Example",
val = 100)
public static void myMeth() { // ...
This annotation is linked
with the method myMeth( ). Look
closely at the annotation syntax. The name of the annotation, preceded by an @,
is followed by a parenthesized list of member initializations. To give a member
a value, that member’s name is assigned a value. Therefore, in the example, the
string "Annotation Example" is assigned to the str member of MyAnno.
Notice that no parentheses follow str
in this assignment. When an annotation member is given a value, only its name
is used. Thus, annotation members look like fields in this context.
Specifying
a Retention Policy
Before exploring annotations
further, it is necessary to discuss annotation
retention policies. A retention policy determines at what point an
annotation is discarded. Java defines three such policies, which are
encapsulated within the java.lang.annotation.RetentionPolicy
enumeration. They are SOURCE, CLASS, and RUNTIME.
An annotation with a
retention policy of SOURCE is
retained only in the source file and is discarded during compilation.
An annotation with a
retention policy of CLASS is stored
in the .class file during
compilation. However, it is not available through the JVM during run time.
An annotation with a
retention policy of RUNTIME is
stored in the .class file during
compilation and is available through the JVM during run time. Thus, RUNTIME retention offers the greatest
annotation persistence.
A retention policy is
specified for an annotation by using one of Java’s built-in annotations: @Retention. Its general form is shown
here:
@Retention(retention-policy)
Here, retention-policy must be one of the previously discussed
enumeration constants. If no retention policy is specified for an annotation,
then the default policy of CLASS is
used.
The following version of MyAnno uses @Retention to specify the RUNTIME
retention policy. Thus, MyAnno will
be available to the JVM during program execution.
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnno {
String str(); int val();
}
Obtaining
Annotations at Run Time by Use of Reflection
Although annotations are
designed mostly for use by other development or deployment tools, if they
specify a retention policy of RUNTIME,
then they can be queried at run time by any Java program through the use of reflection. Reflection is the feature
that enables information about a class to be obtained at run time. The
reflection API is contained in the java.lang.reflect
package. There are a number of ways to use reflection, and we won’t examine
them all here. We will, however, walk through a few examples that apply to
annotations.
The first step to using reflection
is to obtain a Class object that
represents the class whose annotations you want to obtain. Class is one of Java’s built-in classes and is defined in java.lang. It is described in detail in
Part II. There are various ways to obtain a
Class object. One of the easiest
is to call getClass( ), which is a
method defined by Object. Its
general form is shown here:
final Class<?>
getClass( )
It returns the Class object
that represents the invoking object.
After you have obtained a Class object, you can use its methods
to obtain information about the various items declared by the class, including
its annotations. If you want to obtain the annotations associated with a
specific item declared within a class, you must first obtain an object that
represents that item. For example, Class
supplies (among others) the getMethod( ), getField( ), and getConstructor( ) methods, which obtain information about a method, field, and constructor,
respectively. These methods return objects of type
Method, Field, and Constructor.
To understand the process,
let’s work through an example that obtains the annotations associated with a
method. To do this, you first obtain a Class
object that represents the class, and then call getMethod( ) on that Class
object, specifying the name of the method. getMethod(
) has this general form:
Method getMethod(String methName, Class<?> ... paramTypes)
The name of the method is
passed in methName. If the method has
arguments, then Class objects
representing those types must also be specified by paramTypes. Notice that paramTypes
is a varargs parameter. This means that you can specify as many parameter types as needed, including zero. getMethod( ) returns a Method object that represents the
method. If the method can’t be found, NoSuchMethodException
is thrown.
From a Class, Method, Field, or Constructor object, you can obtain a specific annotation associated
with that object by calling getAnnotation(
). Its general form is shown here:
<A extends Annotation>
getAnnotation(Class<A> annoType)
Here, annoType is a Class
object that represents the annotation in which you are interested. The method
returns a reference to the annotation. Using this reference, you can obtain the
values associated with the annotation’s members. The method returns null if the annotation is not found,
which will be the case if the annotation does not have RUNTIME retention.
Here is a program that
assembles all of the pieces shown earlier and uses reflection to display the
annotation associated with a method:
import java.lang.annotation.*;
import java.lang.reflect.*;
// An annotation type declaration.
@Retention(RetentionPolicy.RUNTIME) @interface
MyAnno {
String str(); int val();
}
class Meta {
// Annotate a method.
@MyAnno(str = "Annotation Example",
val = 100)
public static void myMeth() { Meta ob = new
Meta();
Obtain the annotation for this method
and display the values of the members.
try {
//First, get a Class object that represents
//this class.
Class<?> c = ob.getClass();
Now, get a Method object that represents
this method.
Method m = c.getMethod("myMeth");
//Next, get the annotation for this class.
MyAnno anno = m.getAnnotation(MyAnno.class);
//Finally, display the values.
System.out.println(anno.str() + " " +
anno.val());
} catch (NoSuchMethodException exc) {
System.out.println("Method Not
Found.");
}
}
public static void main(String args[]) {
myMeth();
}
}
The output from the program
is shown here:
Annotation Example 100
This program uses reflection
as described to obtain and display the values of str and val in the MyAnno annotation associated with myMeth( ) in the Meta class. There are two things to pay special attention to.
First, in this line
MyAnno anno = m.getAnnotation(MyAnno.class);
notice the expression MyAnno.class. This expression evaluates
to a Class object of type MyAnno, the annotation. This construct
is called a class literal. You can use this type of expression whenever a Class
object of a known class is needed. For example, this statement could have been
used to obtain the Class object for Meta:
Class<?> c = Meta.class;
Of course, this approach only
works when you know the class name of an object in advance, which might not
always be the case. In general, you can obtain a class literal for classes,
interfaces, primitive types, and arrays. (Remember, the <?> syntax
relates to Java’s generics feature. It is described in Chapter 14.)
The second point of interest
is the way the values associated with str
and val are obtained when they are
output by the following line:
System.out.println(anno.str() + " " +
anno.val());
Notice that they are invoked
using the method-call syntax. This same approach is used whenever the value of
an annotation member is required.
A
Second Reflection Example
In the preceding example, myMeth( ) has no parameters. Thus, when
getMethod( ) was called, only the
name myMeth was passed. However, to
obtain a method that has parameters, you must specify class objects
representing the types of those parameters as arguments to getMethod( ). For example, here is a slightly different version of
the preceding program:
import java.lang.annotation.*;
import java.lang.reflect.*;
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnno {
String str(); int val();
}
class Meta {
// myMeth now has two arguments.
@MyAnno(str = "Two Parameters", val =
19)
public static void myMeth(String str, int i)
{
Meta ob = new Meta();
try {
Class<?> c = ob.getClass();
// Here, the parameter types are specified.
Method m = c.getMethod("myMeth", String.class, int.class);
MyAnno anno = m.getAnnotation(MyAnno.class);
System.out.println(anno.str() + " " +
anno.val()); } catch (NoSuchMethodException exc) {
System.out.println("Method Not
Found.");
}
}
public static void main(String args[]) {
myMeth("test", 10);
}
}
The output from this version
is shown here:
Two Parameters 19
In this version, myMeth( ) takes a String and an int
parameter. To obtain information about this method, getMethod( ) must be called as shown here:
Method m = c.getMethod("myMeth",
String.class, int.class);
Here, the Class objects representing String and int are passed as additional arguments.
Obtaining
All Annotations
You can obtain all
annotations that have RUNTIME
retention that are associated with an item by calling getAnnotations( ) on that item. It has this general form:
Annotation[ ] getAnnotations(
)
It returns an array of the
annotations. getAnnotations( ) can
be called on objects of type
Class, Method, Constructor, and Field, among others.
Here is another reflection
example that shows how to obtain all annotations associated with a class and
with a method. It declares two annotations. It then uses those annotations to
annotate a class and a method.
// Show all annotations for a class and a
method.
import java.lang.annotation.*;
import java.lang.reflect.*;
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnno {
String str(); int val();
}
@Retention(RetentionPolicy.RUNTIME) @interface
What {
String description();
}
@What(description = "An annotation test
class") @MyAnno(str = "Meta2", val = 99)
class Meta2 {
@What(description = "An annotation test
method") @MyAnno(str = "Testing", val = 100)
public static void myMeth() { Meta2 ob = new
Meta2();
try {
Annotation annos[] =
ob.getClass().getAnnotations();
// Display all annotations for Meta2.
System.out.println("All annotations for
Meta2:"); for(Annotation a : annos)
System.out.println(a);
System.out.println();
// Display all annotations
for myMeth.
Method m = ob.getClass(
).getMethod("myMeth");
annos = m.getAnnotations();
System.out.println("All annotations for
myMeth:");
for(Annotation a : annos)
System.out.println(a);
} catch (NoSuchMethodException exc) {
System.out.println("Method Not
Found.");
}
}
public static void main(String args[]) {
myMeth();
}
}
The output is shown here:
All annotations for Meta2: @What(description=An
annotation test class) @MyAnno(str=Meta2, val=99)
All annotations for myMeth:
@What(description=An annotation test method) @MyAnno(str=Testing, val=100)
The program uses getAnnotations( ) to obtain an array of
all annotations associated with the Meta2
class and with the myMeth( ) method.
As explained, getAnnotations( )
returns an array of Annotation
objects. Recall that Annotation is a
super-interface of all annotation interfaces and that it overrides toString( ) in Object. Thus, when a reference to an Annotation is output, its toString(
) method is called to generate a string that describes the annotation, as
the preceding output shows.
The
AnnotatedElement Interface
The methods getAnnotation( ) and getAnnotations( ) used by the preceding
examples are defined by the AnnotatedElement
interface, which is defined in java.lang.reflect.
This interface supports reflection for annotations and is implemented by the
classes Method,
Field, Constructor, Class, and Package, among others.
In addition to getAnnotation( ) and getAnnotations( ), AnnotatedElement defines several other methods. Two have been
available since JDK 5. The first is getDeclaredAnnotations(
), which has this general form:
Annotation[ ]
getDeclaredAnnotations( )
It returns all non-inherited
annotations present in the invoking object. The second is isAnnotationPresent( ), which has this general form:
boolean
isAnnotationPresent(Class<? extends Annotation> annoType)
It returns true if the annotation specified by annoType is associated with the invoking
object. It returns false otherwise.
To these, JDK 8 adds getDeclaredAnnotation(
), getAnnotationsByType( ), and getDeclaredAnnotationsByType( ). Of
these, the last two automatically
work with a repeated annotation.(Repeated annotations are discussed at the end
of this chapter.)
Using
Default Values
You can give annotation
members default values that will be used if no value is specified when the
annotation is applied. A default value is specified by adding a default clause to a member’s
declaration. It has this general form:
type member( ) default value ;
Here, value must be of a type compatible with type.
Here is @MyAnno rewritten to include default values:
// An annotation type declaration that includes
defaults. @Retention(RetentionPolicy.RUNTIME)
@interface MyAnno {
String str() default "Testing";
int val() default 9000;
}
This declaration gives a
default value of "Testing" to str
and 9000 to val. This means that
neither value needs to be specified when @MyAnno
is used. However, either or both can be given values if desired. Therefore,
following are the four ways that @MyAnno
can be used:
@MyAnno() // both str and val default
@MyAnno(str = "some string")
// val defaults
@MyAnno(val = 100) // str defaults
@MyAnno(str = "Testing", val = 100)
// no defaults
The following program
demonstrates the use of default values in an annotation.
import java.lang.annotation.*;
import java.lang.reflect.*;
// An annotation type declaration that includes
defaults.
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnno {
String str() default "Testing";
int val() default 9000;
}
class Meta3 {
// Annotate a method using the default values.
@MyAnno()
public static void myMeth() {
Meta3 ob = new Meta3();
//Obtain the annotation for this method and
display the values of the members.
try {
Class<?> c = ob.getClass();
Method m = c.getMethod("myMeth");
MyAnno anno = m.getAnnotation(MyAnno.class);
System.out.println(anno.str() + " " +
anno.val()); }
catch (NoSuchMethodException exc) {
System.out.println("Method Not
Found.");
}
}
public static void main(String args[]) {
myMeth();
}
}
The output is shown here:
Testing 9000
Marker
Annotations
A marker annotation is a special kind of annotation that contains no
members. Its sole purpose is to mark an item. Thus, its presence as an
annotation is sufficient. The best way to determine if a marker annotation is
present is to use the method isAnnotationPresent(
), which is defined by the AnnotatedElement
interface.
Here is an example that uses
a marker annotation. Because a marker interface contains no members, simply
determining whether it is present or absent is sufficient.
import java.lang.annotation.*;
import java.lang.reflect.*;
// A marker annotation.
@Retention(RetentionPolicy.RUNTIME) @interface
MyMarker { }
class Marker {
//Annotate a method using a marker.
//Notice that no ( ) is needed.
@MyMarker
public static void myMeth() { Marker ob = new
Marker();
try {
Method m =
ob.getClass().getMethod("myMeth");
// Determine if the annotation is present.
if(m.isAnnotationPresent(MyMarker.class))
System.out.println("MyMarker is
present.");
} catch (NoSuchMethodException exc) {
System.out.println("Method Not Found.");
}
}
public static void main(String args[]) {
myMeth();
}
}
The output, shown here,
confirms that @MyMarker is present:
MyMarker is present.
In the program, notice that
you do not need to follow @MyMarker
with parentheses when it is applied. Thus, @MyMarker
is applied simply by using its name, like this:
@MyMarker
It is not wrong to supply an
empty set of parentheses, but they are not needed.
Single-Member
Annotations
A single-member annotation contains only one member. It works like a
normal annotation except that it allows a shorthand form of specifying the
value of the member. When only one member is present, you can simply specify
the value for that member when the annotation is applied—you don’t need to
specify the name of the member. However,
in order to use this
shorthand, the name of the member must be value.
Here is an example that creates and uses a single-member annotation:
import java.lang.annotation.*;
import java.lang.reflect.*;
// A single-member annotation.
@Retention(RetentionPolicy.RUNTIME) @interface
MySingle {
int value(); // this variable name must be
value
}
class Single {
// Annotate a method using a single-member
annotation.
@MySingle(100)
public static void myMeth() { Single ob = new
Single();
try {
Method m = ob.getClass().getMethod("myMeth");
MySingle anno =
m.getAnnotation(MySingle.class);
System.out.println(anno.value()); // displays
100
} catch (NoSuchMethodException exc) {
System.out.println("Method Not Found.");
}
public static void main(String args[]) {
myMeth();
}
}
As expected, this program
displays the value 100. In the program, @MySingle
is used to annotate myMeth( ), as
shown here:
@MySingle(100)
Notice that value = need not be specified.
You can use the single-value
syntax when applying an annotation that has other members, but those other
members must all have default values. For example, here the value xyz is added, with a default value of
zero:
@interface SomeAnno { int value();
int xyz() default 0;
}
In cases in which you want to
use the default for xyz, you can
apply @SomeAnno, as shown next, by
simply specifying the value of value
by using the single-member syntax.
@SomeAnno(88)
In this case, xyz defaults to zero, and value gets the value 88. Of course, to
specify a different value for xyz
requires that both members be explicitly named, as shown here:
@SomeAnno(value = 88, xyz = 99)
Remember, whenever you are
using a single-member annotation, the name of that member must be value.
The
Built-In Annotations
Java defines many built-in
annotations. Most are specialized, but nine are general purpose. Of these, four
are imported from java.lang.annotation:
@Retention, @Documented, @Target,
and @Inherited. Five—@Override, @Deprecated,
@FunctionalInterface,
@SafeVarargs, and @SuppressWarnings—are
included in java.lang. Each is
described here.
@Retention
@Retention is designed to be used only as an annotation to another annotation.
It specifies the retention policy as
described earlier in this chapter.
@Documented
The @Documented annotation is a marker interface that tells a tool that
an annotation is to be documented. It is designed to be used only as an
annotation to an annotation declaration.
@Target
The @Target annotation specifies the types of items to which an
annotation can be applied. It is designed to be used only as an annotation to
another annotation. @Target takes
one argument, which is an array of constants of the ElementType enumeration. This argument specifies the types of
declarations to which the annotation can be applied. The constants are shown
here along with the type of declaration to which they correspond:
You can specify one or more
of these values in a @Target
annotation. To specify multiple values, you must specify them within a
braces-delimited list. For example, to specify that an annotation applies only
to fields and local variables, you can use this @Target annotation:
@Target( { ElementType.FIELD,
ElementType.LOCAL_VARIABLE } )
If you don't use @Target, then, except for type
parameters, the annotation can be used on any declaration. For this reason, it
is often a good idea to explicitly specify the target or targets so as to
clearly indicate the intended uses of an annotation.
@Inherited
@Inherited is a marker annotation that can be used only on another annotation
declaration. Furthermore, it affects
only annotations that will be used on class declarations. @Inherited causes the annotation for a superclass to be inherited
by a subclass. Therefore, when a request for a specific annotation is made to
the subclass, if that annotation is not present in the subclass, then its
superclass is checked. If that annotation is present in the superclass, and if
it is annotated with @Inherited,
then that annotation will be returned.
@Override
@Override is a marker annotation that can be used only on methods. A method
annotated with @Override must override a method from a superclass. If it doesn’t,
a compile-time error will result. It is used to ensure that a superclass method
is actually overridden, and not simply overloaded.
@Deprecated
@Deprecated is a marker annotation. It indicates that a declaration is obsolete
and has been replaced by a newer
form.
@FunctionalInterface
@FunctionalInterface is a marker annotation added by JDK 8 and
designed for use on interfaces. It
indicates that the annotated interface is a functional interface. A functional interface is an interface that contains one and only one abstract
method. Functional interfaces are
used by lambda expressions. (See Chapter 15 for details on functional
interfaces and lambda expressions.) If the annotated interface is not a
functional interface, a compilation error will be reported. It is important to
understand that @FunctionalInterface
is not needed to create a functional interface. Any interface with exactly one
abstract method is, by definition, a functional interface. Thus, @FunctionalInterface is purely
informational.
@SafeVarargs
@SafeVarargs is a marker annotation that can be applied to methods and
constructors. It indicates that no
unsafe actions related to a varargs parameter occur. It is used to suppress
unchecked warnings on otherwise safe code as it relates to non-reifiable vararg
types and parameterized array instantiation. (A non-reifiable type is,
essentially, a generic type. Generics are described in Chapter 14.) It must be
applied only to vararg methods or constructors that are static or final.
@SuppressWarnings
@SuppressWarnings specifies that one or more warnings that might
be issued by the compiler are to be
suppressed. The warnings to suppress are specified by name, in string form.
Related Topics
Privacy Policy, Terms and Conditions, DMCA Policy and Compliant
Copyright © 2018-2023 BrainKart.com; All Rights Reserved. Developed by Therithal info, Chennai.