Chapter 15. Annotations

 

I don't like spinach, and I'm glad I don't, because if I liked it I'd eat it, and I just hate it.

 
 --Clarence Darrow

The source code for our programs is usually accompanied by copious amounts of informal documentation that typically is contained within comments in the source code file. For example, many organizations have a standard preamble that they place at the top of class source files that contains things like copyright information, the programmers name, the date the class was created, the date the class was last modified, the current revision number, and so forth. Other comments may reflect intended usage of a class or a method, or usage limitations, such as documenting that the instances of a class are not thread-safe and so shouldn't be used concurrently. Other comments may be provided for processing by external tools that assist in the management and deployment of an application, such as version control in a source code management system, or deployment descriptors on how a class should be managed by an application server. These comment-based annotations serve a useful purpose, but they are informal and ad hoc. A better way to document these things is to annotate the program elements directly using annotation types to describe the form of the annotations. Annotation types present the information in a standard and structured way that is amenable to automated processing by tools.

While this chapter presents the syntax and semantics of using annotations and defining annotation types, it can't tell you what annotations to use when because very few such annotations exist within the language. The annotations you use will be those supported by the tools in your development or deployment environment. Annotation is centered on types in the package java.lang.annotation, and the standard types introduced in this chapter come from this package.

A Simple Annotation Example

Consider an informal, comment-based class preamble:

/*-------------------------------
  Created:          Jan 31 2005
  Created By:       James Gosling
  Last Modified:    Feb 9 2005
  Last Modified By: Ken Arnold
  Revision:         3
---------------------------------*/
public class Foo {
    // ...
}

You can define an annotation type that can hold all of the desired information:

@interface ClassInfo {
    String created();
    String createdBy();
    String lastModified();
    String lastModifiedBy();
    int revision();
}

An annotation type is a special kind of interface, designated by the @ character preceding the interface keyword.[1] Annotations are applied to program elements, such as a class or field declaration. What appear to be methods in the annotation type are actually special annotation element declarations. An annotation element is rather like a field, and has a value for each program element that has that type of annotation. So a ClassInfo annotation contains four string elements and one int element. When you annotate (apply an annotation to) a program element, you supply values for each of the annotation elements. Annotations are modifiers and can appear anywhere a modifier is allowed. Here's how you would use ClassInfo with class Foo:

@ClassInfo (
    created = "Jan 31 2005",
    createdBy = "James Gosling",
    lastModified = "Feb 9 2005",
    lastModifiedBy = "Ken Arnold",
    revision = 3
)
public class Foo {
    // ...
}

The annotation is introduced using the @ character again, followed by the name of the annotation type. Values for the annotation elements are provided by a comma-separated list of name=value statements within parentheses. As you can see, an annotation can contain a lot of textual information that can easily obscure the actual program, so it is strongly recommended that you follow a consistent practice of always specifying annotations first, ahead of any other modifiers, and always on a separate line.

To use the ClassInfo annotation, a programmer should update the value for each changed element whenever they edit the source for Foo. Such updates could be automated through development tools that understand annotations.

Annotation Types

Annotation types are a special kind of interface, declared, as you have seen, with the interface keyword preceded by the @ character. Annotation types can be declared anywhere an interface can be declared—that is, as a top-level annotation type or nested within another type—and can have the same modifiers applied as interfaces. Characterizing annotation types as interfaces is a little misleading, however, as aside from borrowing some syntax and some associated usage rules, annotation types bear little resemblance to interfaces in normal use.[2]

The methods declared in an annotation type are known as the elements of the annotation type. These elements are constrained by strict rules:

  • The type of the element can only be a primitive type, String, an enum type, another annotation type, Class (or a specific generic invocation of Class), or an array of one of the preceding types.

  • The element cannot declare any parameters.

  • The element cannot have a throws clause.

  • The element cannot define a type parameter (that is, it can't be a generic method).

In essence, the elements of an annotation type are like the fields of an object, that is instantiated for each program element the annotation type is applied to. The values of these fields are determined by the initializers given when the annotation is applied or by the default value of the element if it has one.

You can give an element a default value by following the empty parameter list with the keyword default and a suitable value. For example, suppose you want to represent a revision number as a pair of integers for the major and minor revision, you could define the following annotation type that has a default revision number of 1.0:

@interface Revision {
    int major() default 1;
    int minor() default 0;
}

The values themselves must be constant expressions or literals (such as a class literal), but not null.

The Revision annotation type could be used to annotate a class or interface—we show you how to enforce this a little later—or used by other annotation types. In particular we can redefine the ClassInfo annotation type to use it:

@interface ClassInfo {
    String created();
    String createdBy();
    String lastModified();
    String lastModifiedBy();
    Revision revision();
}

When we first created class Foo we might have used ClassInfo:

@ClassInfo (
    created = "Jan 31 2005",
    createdBy = "James Gosling",
    lastModified = "Jan 31 2005",
    lastModifiedBy = "James Gosling",
    revision = @Revision
)
public class Foo {
    // ...
}

Notice how the initialization expression for an element of an annotation type uses the annotation syntax. In this case @Revision initializes the revision element with the default values for major and minor. When Foo is later edited, the revision element would be changed accordingly, for example:

@ClassInfo (
    created = "Jan 31 2005",
    createdBy = "James Gosling",
    lastModified = "Feb 9 2005",
    lastModifiedBy = "Ken Arnold",
    revision = @Revision(major = 3)
)
public class Foo {
    // ...
}

This updates the revision to be 3.0, by specifying the major value and again allowing the minor value to default to 0.

An annotation type can have zero elements, in which case it is called a marker annotation—similar to a marker interface. For example, the annotation type java.lang.Deprecated is a marker annotation that identifies a program element that should no longer be used. The compiler can issue a warning when a program element annotated with @Deprecated is used, as could an annotation-aware development environment.

If an annotation type has but a single element, then that element should be named value. This permits some syntactic shorthand that is described in the next section.

There are further restrictions on the form of an annotation type. An annotation type may not explicitly declare that it extends another interface, but all annotation types implicitly extend the interface Annotation. An annotation type is not allowed to be a generic type. Finally, an annotation type is not allowed to directly, or indirectly, have an element of its own type.[3]

If an interface explicitly extends Annotation it is not an annotation type—and Annotation itself is not an annotation type. Similarly, if an interface declares that it extends an annotation type, that interface is not itself an annotation type. Extending or implementing an annotation type is pointless, but if you do it, the annotation type is just a plain interface with the declared set of methods (including those inherited from Annotation).

Finally, just like any other interface, an annotation type can declare constants and (implicitly static) nested types.

Annotating Elements

The program elements that can be annotated are all those for which modifiers can be specified: type declarations (classes, interfaces, enums, and annotation types), field declarations, method and constructor declarations, local variable declarations, and even parameter declarations. There is also a special mechanism to annotate packages, described in Section 18.5 on page 476.

As you have seen, to annotate an element you provide the name of the annotation type being applied, preceded by @ and followed by a parenthesized list of initializers for each element of the annotation type. A given program element can only be annotated once per annotation type.

If the annotation type is a marker annotation or if all its elements have default values, then the list of initializers can be omitted. For example, you can mark a method as deprecated:

@Deprecated
public void badMethod() { /* ... */ }

Equivalently, you can just specify an empty list of initializers:

@Deprecated()
public void badMethod() { /* ... */ }

Otherwise, for each element that does not have a default value you must list an initializer, of the form name=value, as you have seen with ClassInfo. The order of initializers is not significant, but each element can only appear once. If an element has a default value then it need not appear, but you can include it if you want to override the default value with a specific one, as you saw in the Revision annotation type with ClassInfo.

If an element has an array type, then it is initialized with an array initializer expression. For example, an annotation to track the bug fixes applied to a class might look like this:

@interface BugsFixed {
    String[] bugIDs();
}

The intent is that as each bug is fixed, its identifier is appended to the initializer list for bugIDs. Here's how it might be used:

@BugsFixed(bugIDs = { "457605", "532456"})
class Foo { /* ... */ }

If an array has only a single element, then you can use a shorthand to initialize the array, dispensing with the braces around the array elements. For example, the first bug fixed in Foo could be annotated like this:

@BugsFixed(bugIDs = "457605")

If an annotation type, like BugsFixed, has only a single element, then naming that element value allows for some additional shorthand:

@interface BugsFixed {
    String[] value();
}

The first use of BugsFixed above can now be written simply as

@BugsFixed({ "457605", "532456"})

If there is a single initializer expression, then it is assumed to initialize an element called value. If there is no such element by that name, then you will get a compile-time error. Combining this shorthand with the shorthand for arrays with one element, you can rewrite the second use of BugsFixed to be

@BugsFixed("457605")

You can have more than one element in the annotation type and still call one of them value. If you do, and all other elements have default values, then you can still use the shorthand form to initialize the value element. However, once you have more than one initializer expression, you must explicitly name each element.

Finally, you can annotate an annotation type with itself. For example, the Documented marker annotation indicates that the program element that is annotated should have its documentation comments processed—see Chapter 19. Because Documented should itself be documented it is annotated with itself:

@Documented
@interface Documented { }

Self-annotation is quite different from having an element of the annotation type within the annotation type—which, as you know, is not permitted.

Restricting Annotation Applicability

Annotations can appear anywhere a modifier is allowed, but as you can imagine, not every annotation makes sense for every program element—consider applying ClassInfo to a method parameter! You can restrict the applicability of an annotation type by annotating it with the @Target annotation. An annotation on an annotation type is known as a meta-annotation.

The Target annotation type is one of a small number of annotation types defined in the java.lang.annotation package—all the types mentioned here are in that package unless otherwise stated. Target is applied to annotation types, and controls where those types are themselves applicable. It has a single element—an array of the enum type ElementType—which following convention is called value. ElementType represents the different kinds of program elements to which annotations can be applied, and defines the constants ANNOTATION_TYPE, CONSTRUCTOR, METHOD, FIELD, LOCAL_VARIABLE, PARAMETER, PACKAGE, and TYPE. The compiler will check that any annotation applied to a program element is allowed to be applied to that kind of program element.

The ClassInfo annotation type should only be applied to type declarations (classes, interfaces, enums, or annotation types), so it should be declared:

@Target(ElementType.TYPE)
@interface ClassInfo {
    String created();
    String createdBy();
    String lastModified();
    String lastModifiedBy();
    Revision revision();
}

Now if you tried to annotate a parameter declaration with ClassInfo, you'd get a compile-time error.

You can specify multiple element types when applying the Target annotation. For example, an annotation type that only applies to fields or local variables would be annotated with

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

Our Revision annotation type could also be annotated with:

@Target(ElementType.TYPE)

to restrict its applicability to type declarations. If an annotation type has no @Target meta-annotation then it is applicable anywhere.

Don't confuse the applicability of an annotation type with the accessibility of the annotation type. If an annotation is public then it can be used anywhere, but its applicability might restrict what elements it can be applied to. Conversely, you can't apply an annotation if it is inaccessible, even if you are trying to apply it to the right kind of element. Restricting the applicability of an annotation does not affect its use within other annotation types. For example, if Revision were restricted to use on local variables (not that it makes sense), that doesn't stop ClassInfo from having an element of type Revision.

Retention Policies

Annotations can serve many different purposes and may be intended for different readers. Most of the annotations you have seen are intended to be read by programmers, or perhaps a development tool. Others, such as @Deprecated, are intended for the compiler to read. In some cases a tool may need to extract the annotations from a binary representation of a class, such as to determine how an application should be deployed. And sometimes annotations will need to be inspected at runtime.

Associated with every annotation type is a retention policy that determines when the annotation can be accessed. This retention policy is defined by the RetentionPolicy enum, and is controlled by the use of the Retention meta-annotation. There are three retention policy values:

  • SOURCE—. Annotations exist only in the source file, and are discarded by the compiler when producing a binary representation.

  • CLASS—. Annotations are preserved in the binary representation of the class, but need not be available at runtime.

  • RUNTIME—. Annotations are preserved in the binary representation of the class and must be available at runtime via the reflection mechanism.

The default retention policy is CLASS. Regardless of the retention policy, annotations on local variables are never available in the binary representation or at runtime. There is no place to store the information in the binary representation.

The reflection methods for accessing annotations at runtime are discussed in Section 16.2 on page 414. In essence, for each annotated element, the runtime system creates an object that implements the interface defined by the annotation type. You get the values of the annotation elements for that program element by invoking the corresponding method on that object.

Working with Annotations

Annotations can be very powerful, but they can easily be misused. It is easy to achieve annotation-overload when the number and verbosity of the annotations totally obscure the code itself. Annotations should be used sparingly and wisely.

The second problem with annotations is that anyone can define their own. A key benefit of annotations is their suitability for automatic analysis, generally via annotation processing tools, or APTs. But automation is most helpful if there are common, standard annotations. While you have seen how annotation types can be defined, in practice very few programmers should need to define their own types. There are currently only a few defined annotation types:[4]

  • The meta-annotations @Target and @Retention, which you have seen.

  • The @Deprecated and @Documented annotations, which you have also seen.

  • The @Inherited meta-annotation that indicates an annotation should be inherited. For example, if a class Foo is queried for a specific annotation type that is not present on Foo, but that annotation type has the @Inherited meta-annotation, then Foo's superclass will be queried, and so forth.

  • The @Override annotation, which informs the compiler that a method is meant to override an inherited method from a superclass. This allows the compiler to warn you if you mistakenly overload the method instead of overriding it (a common mistake).

  • The @SuppressWarnings annotation tells the compiler to ignore certain warnings, as defined by the strings used to initialize the annotation. Unfortunately, there is no predefined set of warning strings, meaning that interoperability between different compilers could be a problem.

Without standards in this area, the effective use of annotations could be stifled.

Finally, another potential problem with annotations is that they could be misused to change the semantics of the code they are applied to. According to the Java Language Specification, “annotations are not permitted to affect the semantics of programs in the Java programming language in any way.” Indeed, any unrecognized annotations should just be ignored by the compiler, the virtual machine, and any other annotation processing tool. However, it is easy to see how, with a suitable processing tool, annotations could be used to achieve a style of metaprogramming, that allows people to create specialized dialects of the Java programming language to meet their own specific needs. If programs become dependent on specialized annotation support, then the portability benefits of the Java platform will be lost.

 

I don't care who does the electin' as long as I get to do the nominatin'.

 
 --Boss Tweed


[1] The @ character was chosen because it is pronounced “at”: A-T, short for Annotation Type.

[2] Except when accessed reflectively at runtime—see “Annotation Queries” on page 414.

[3] This restriction is aimed at preventing the possibility of infinite recursion if an annotation type had an element of its own type with a default value. Curiously, there are no attempts to prevent a similar infinite recursion with class types.

[4] Under the Java Community Process, JSR 250 is defining common annotations for future use.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.139.82.4