Saturday, January 30, 2016

Topic 34: Annotations (Metadata) in Java

  • We can embed supplemental information into a source file. This information, called an annotation( or metadata), does not change the actions of a program. 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.

  • 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 without body. Java implements these methods. Moreover, the methods act much like fields.

  • 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.

  • 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 declaration.

  • When we apply an annotation, we give values to its members. For example, here is an example of MyAnno being applied to a method:

// Annotate a method.
@MyAnno(str = "Annotation Example", val = 100)
public static void 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.

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

  • 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.

    • The first step to using reflection is to obtain a Class object that represents the class whose annotations we want to obtain. (Class is one of Java’s built-in classes and is defined in java.lang. 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 we have obtained a Class object, we can use its methods to obtain information about the various items declared by the class, including its annotations. If we want to obtain the annotations associated with a specific item declared within a class, we  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.

An example that obtains the annotations associated with a method. 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. 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, we can obtain a specific annotation associated with that object by calling getAnnotation( ). Its general form is shown here:

Annotation getAnnotation(Class annoType)

Here, annoType is a Class object that represents the annotation in which we are interested. The method returns a reference to the annotation. Using this reference, we 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

In the program, the expression MyAnno.class evaluates to a Class object of type MyAnno, the annotation. This construct is called a class literal. We 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 we know the class name of an object in advance, which might not always be the case. In general, we can obtain a class literal for classes, interfaces, primitive types, and arrays.

  • 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, we 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:

// myMeth now has two arguments.
@MyAnno(str = "Two Parameters", val = 19)
public static void myMeth(String str, int i)
//...
// Here, the parameter types are specified.
Method m = c.getMethod("myMeth", String.class, int.class); //
The Class objects representing String and int are passed as additional arguments.

//...
public static void main(String args[]) {
myMeth("test", 10);
}

  • We 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.

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 methods getAnnotation( ) and getAnnotations( ) are defined by the AnnotatedElement interface, which is defined in java.lang.reflect.
    In addition, AnnotatedElement defines two other methods. 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 annoType)

It returns true if the annotation specified by annoType is associated with the invoking object. It returns false otherwise.

  • We 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;// 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

  • A marker annotation is a special kind of annotation that contains no members. Its sole purpose is to mark a declaration. The best way to determine if a marker annotation is present is to use the method isAnnotationPresent( ), which is a 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();
}
}

It is not wrong to supply an empty set of parentheses when linking marker with the method, but they are not needed.

  • 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, we can simply specify the value for that member when the annotation is applied—we 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:

// A single-member annotation.
@Retention(RetentionPolicy.RUNTIME)
@interface MySingle {
int value(); // this variable name must be value
}
//...
// Annotate a method using a single-member annotation.
@MySingle(100) //Notice that value = need not be specified.
public static void myMeth() {
//...


We 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 we want to use the default for xyz, we can apply @SomeAnno 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)

  • Java defines many built-in annotations. Of these, four are imported from java.lang.annotation: @Retention, @Documented, @Target, and @Inherited. Three—@Override, @Deprecated, and @SuppressWarnings—are included in java.lang.

  • @Retention - @Retention is designed to be used only as an annotation to another annotation. It specifies the retention policy.

  • @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 declarations 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 must be a constant from 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.

  • ANNOTATION_TYPE - Another annotation

  • CONSTRUCTOR - Constructor

  • FIELD - Field

  • LOCAL_VARIABLE - Local variable

  • METHOD - Method

  • PACKAGE - Package

  • PARAMETER - Parameter

  • TYPE - Class, interface, or enumeration

We can specify one or more of these values in a @Target annotation. To specify multiple values, we must specify them within a braces-delimited list. For example, to specify that an annotation applies only to fields and local variables, we can use this @Target annotation:

@Target( { ElementType.FIELD, ElementType.LOCAL_VARIABLE } )

  • @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.

  • @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. This annotation can be applied to any type of declaration.

  • There are a number of restrictions that apply to annotation declarations:

  • First, no annotation can inherit another.

  • Second, all methods declared by an annotation must be without parameters. Furthermore, they must return one of the following:

  • A primitive type, such as int or double

  • An object of type String or Class

  • An enum type

  • Another annotation type

  • An array of one of the preceding types

  • Annotations cannot be generic. In other words, they cannot take type parameters.

  •   Finally, annotation methods cannot specify a throws clause.


Click for NEXT article.


Please feel free to correct me by commenting your suggestions and feedback.


No comments:

Post a Comment