Annotations.....................................................................................................................................1 Documentation............................................................................................................................2 Annotations Used by the Compiler.............................................................................................3 Annotation Processing................................................................................................................5 Questions.....................................................................................................................................5 Answers to Questions ................................................................................................................6 More Info from Sun site: ............................................................................................................8
Annotations Annotations provide data about a program that is not part of the program itself. They have no direct effect on the operation of the code they annotate.
Annotations have a number of uses, among them: • • •
Information for the compiler — Annotations can be used by the compiler to detect errors or suppress warnings. Compiler-time and deployment-time processing — Software tools can process annotation information to generate code, XML files, and so forth. Runtime processing — Some annotations are available to be examined at runtime.
Annotations can be applied to a program's declarations of classes, fields, methods, and other program elements. The annotation appears first, often (by convention) on its own line, and may include elements with named or unnamed values: @Author( name = "Benjamin Franklin", date = "3/27/2003" ) class MyClass() { }
or @SuppressWarnings(value = "unchecked") void myMethod() { }
If there is just one element named "value," then the name may be omitted, as in: @SuppressWarnings("unchecked") void myMethod() { }
Also, if an annotation has no elements, the parentheses may be omitted, as in: @Override void mySuperMethod() { }
Documentation Many annotations replace what would otherwise have been comments in code. Suppose that a software group has traditionally begun the body of every class with comments providing important information: public class Generation3List extends Generation2List { // // // // // //
Author: John Doe Date: 3/17/2002 Current revision: 6 Last modified: 4/12/2004 By: Jane Doe Reviewers: Alice, Bill, Cindy
// class code goes here }
To add this same metadata with an annotation, you must first define the annotation type. The syntax for doing this is: @interface ClassPreamble { String author(); String date(); int currentRevision() default 1; String lastModified() default "N/A"; String lastModifiedBy() default "N/A"; String[] reviewers(); // Note use of array }
The annotation type definition looks somewhat like an interface definition where the keyword interface is preceded by the @ character (@ = "AT" as in Annotation Type). Annotation types are, in fact, a form of interface, which will be covered in a later lesson. For the moment, you do not need to understand interfaces. The body of the annotation definition above contains annotation type element declarations, which look a lot like methods. Note that they may define optional default values. Once the annotation type has been defined, you can use annotations of that type, with the values filled in, like this: @ClassPreamble ( author = "John Doe", date = "3/17/2002", currentRevision = 6, lastModified = "4/12/2004", lastModifiedBy = "Jane Doe" reviewers = {"Alice", "Bob", "Cindy"} // Note array notation ) public class Generation3List extends Generation2List { // class code goes here }
Note: To make the information in @ClassPreamble appear in Javadoc-generated documentation, you must annotate the @ClassPreamble definition itself with the @Documented annotation: import java.lang.annotation.*; // import this to use @Documented @Documented @interface ClassPreamble {
// Annotation element definitions }
Annotations Used by the Compiler
There are three annotation types that are predefined by the language specification itself: @Deprecated, @Override, and @SuppressWarnings.
@Deprecated—the
annotation indicates that the marked element is deprecated and should no longer be used. The compiler generates a warning whenever a program uses a method, class, or field with the @Deprecated annotation. When an element is deprecated, it should also be documented using the Javadoc @deprecated tag, as shown in the following example. The use of the "@" symbol in both Javadoc comments and in annotations is not coincidental—they are related conceptually. Also, note that the Javadoc tag starts with a lowercase "d" and the annotation starts with an uppercase "D".
}
@Deprecated
// Javadoc comment follows /** * @deprecated * explanation of why it was deprecated */ @Deprecated static void deprecatedMethod() { }
@Override—the
@Override annotation informs the compiler that the element is meant to override an element declared in a superclass (overriding methods will be discussed in the lesson titled "Interfaces and Inheritance"). // mark method as a superclass method // that has been overridden @Override int overriddenMethod() { }
While it's not required to use this annotation when overriding a method, it helps to prevent errors. If a method marked with @Override fails to correctly override a method in one of its superclasses, the compiler generates an error.
@SuppressWarnings—the
@SuppressWarnings annotation tells the compiler to suppress specific warnings that it would otherwise generate. In the example below, a deprecated method is used and the compiler would normally generate a warning. In this case, however, the annotation causes the warning to be suppressed. // use a deprecated method and tell // compiler not to generate a warning @SuppressWarnings("deprecation") void useDeprecatedMethod() { objectOne.deprecatedMethod(); //deprecation warning - suppressed }
Every compiler warning belongs to a category. The Java Language Specification lists two categories: "deprecation" and "unchecked." The "unchecked" warning can occur when interfacing with legacy code written before the advent of generics (discussed in the lesson titled "Generics"). To suppress more than one category of warnings, use the following syntax:
@SuppressWarnings({"unchecked", "deprecation"})
Annotation Processing The more advanced uses of annotations include writing an annotation processor that can read a Java program and take actions based on its annotations. It might, for example, generate auxiliary source code, relieving the programmer of having to create boilerplate code that always follows predictable patterns. To facilitate this task, release 5.0 of the JDK includes an annotation processing tool, called apt. In release 6 of the JDK, the functionality of apt is a standard part of the Java compiler. To make annotation information available at runtime, the annotation type itself must be annotated with @Retention(RetentionPolicy.RUNTIME), as follows: import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) @interface AnnotationForRuntime { // Elements that give information // for runtime processing }
Questions and Exercises: Annotations Questions 1. What is wrong with the following interface?
public interface House { @Deprecated void open(); void openFrontDoor(); void openBackDoor(); }
2. Compile this program: interface Closable { void close(); } class File implements Closable { @Override public void close() { //... close this file... } }
What happens? Can you explain why? 3. Consider this implementation of the House interface, shown in Question 1. public class MyHouse implements House { public void open() {} public void openFrontDoor() {} public void openBackDoor() {} }
If you compile this program, the compiler complains that open has been deprecated (in the interface). What can you do to get rid of that warning?
Answers to Questions Question 1: What is wrong with the following interface: public interface House { @Deprecated
}
public void open(); public void openFrontDoor(); public void openBackDoor();
Answer 1:The documentation should reflect why open is deprecated and what to use instead. For example: public interface House { /** * @deprecated use of open is discouraged, use * openFrontDoor or openBackDoor instead. */ @Deprecated public void open(); public void openFrontDoor(); public void openBackDoor(); }
Question 2: Compile this program: interface Closable { void close(); } class File implements Closable { @Override public void close() { //... close this file... } }
What happens? Can you explain why? Answer 2: The compiler generates an error complaining that File.close doesn't override any method from its superclass. This is because it is not overriding Closable.close, it is implementing it! Question 3: Consider this implementation of the House interface, shown in Question 1. public class MyHouse implements House { public void open() {} public void openFrontDoor() {} public void openBackDoor() {} }
If you compile this program, the compiler complains that open has been deprecated (in the interface). What can you do to get rid of that warning? Answer 3: You can deprecate the implementation of open: public class MyHouse implements House { //The documentation is inherited from the interface. @Deprecated
}
public void open() {} public void openFrontDoor() {} public void openBackDoor() {}
Alternatively, you can suppress the warning: public class MyHouse implements House { @SuppressWarnings("deprecation") public void open() {} public void openFrontDoor() {} public void openBackDoor() {} }
More Info from Sun site: Many APIs require a fair amount of boilerplate code. For example, in order to write a JAX-RPC web service, you must provide a paired interface and implementation. This boilerplate could be generated automatically by a tool if the program were “decorated” with annotations indicating which methods were remotely accessible. Other APIs require “side files” to be maintained in parallel with programs. For example JavaBeans requires a BeanInfo class to be maintained in parallel with a bean, and Enterprise JavaBeans (EJB) requires a deployment descriptor. It would be more convenient and less error-prone if the information in these side files were maintained as annotations in the program itself. The Java platform has always had various ad hoc annotation mechanisms. For example the transient modifier is an ad hoc annotation indicating that a field should be ignored by the serialization subsystem, and the @deprecated javadoc tag is an ad hoc annotation indicating that the method should no longer be used. As of release 5.0, the platform has a general purpose annotation (also known as metadata) facility that permits you to define and use your own annotation types. The facility consists of a syntax for declaring annotation types, a syntax for annotating declarations, APIs for reading annotations, a class file representation for annotations, and an annotation processing tool. Annotations do not directly affect program semantics, but they do affect the way programs are treated by tools and libraries, which can in turn affect the semantics of the running program. Annotations can be read from source files, class files, or reflectively at run time. Annotations complement javadoc tags. In general, if the markup is intended to affect or produce documentation, it should probably be a javadoc tag; otherwise, it should be an annotation. Typical application programmers will never have to define an annotation type, but it is not hard to do so. Annotation type declarations are similar to normal interface declarations. An at-sign (@) precedes the interface keyword. Each method declaration defines an element of the annotation
type. Method declarations must not have any parameters or a throws clause. Return types are restricted to primitives, String, Class, enums, annotations, and arrays of the preceding types. Methods can have default values. Here is an example annotation type declaration: /** * Describes the Request-For-Enhancement(RFE) that led * to the presence of the annotated API element. */ public @interface RequestForEnhancement { int id(); String synopsis(); String engineer() default "[unassigned]"; String date(); default "[unimplemented]"; }
Once an annotation type is defined, you can use it to annotate declarations. An annotation is a special kind of modifier, and can be used anywhere that other modifiers (such as public, static, or final) can be used. By convention, annotations precede other modifiers. Annotations consist of an at-sign (@) followed by an annotation type and a parenthesized list of element-value pairs. The values must be compile-time constants. Here is a method declaration with an annotation corresponding to the annotation type declared above: @RequestForEnhancement( id = 2868724, synopsis = "Enable time-travel", engineer = "Mr. Peabody", date = "4/1/3007" ) public static void travelThroughTime(Date destination) { ... }
An annotation type with no elements is termed a marker annotation type, for example: /** * Indicates that the specification of the annotated API element * is preliminary and subject to change. */ public @interface Preliminary { }
It is permissible to omit the parentheses in marker annotations, as shown below: @Preliminary public class TimeTravel { ... }
In annotations with a single element, the element should be named value, as shown below: /** * Associates a copyright notice with the annotated API element. */ public @interface Copyright { String value(); }
It is permissible to omit the element name and equals sign ( =) in a single-element annotation whose element name is value, as shown below: @Copyright("2002 Yoyodyne Propulsion Systems") public class OscillationOverthruster { ... }
To tie it all together, we'll build a simple annotation-based test framework. First we need a marker annotation type to indicate that a method is a test method, and should be run by the testing tool: import java.lang.annotation.*; /** * Indicates that the annotated method is a test method. * This annotation should be used only on parameterless static methods. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Test { }
Note that the annotation type declaration is itself annotated. Such annotations are called metaannotations. The first (@Retention(RetentionPolicy.RUNTIME)) indicates that annotations with this type are to be retained by the VM so they can be read reflectively at run-time. The second (@Target(ElementType.METHOD)) indicates that this annotation type can be used to annotate only method declarations. Here is a sample program, some of whose methods are annotated with the above interface: public class Foo { @Test public static void m1() { } public static void m2() { } @Test public static void m3() { throw new RuntimeException("Boom"); } public static void m4() { } @Test public static void m5() { } public static void m6() { } @Test public static void m7() { throw new RuntimeException("Crash"); } public static void m8() { } }]
Here is the testing tool: import java.lang.reflect.*; public class RunTests { public static void main(String[] args) throws Exception { int passed = 0, failed = 0; for (Method m : Class.forName(args[0]).getMethods()) { if (m.isAnnotationPresent(Test.class)) {
try { m.invoke(null); passed++; } catch (Throwable ex) { System.out.printf("Test %s failed: %s %n", m, ex.getCause()); failed++; }
} } System.out.printf("Passed: %d, Failed %d%n", passed, failed); }
}
The tool takes a class name as a command line argument and iterates over all the methods of the named class attempting to invoke each method that is annotated with the Test annotation type (defined above). The reflective query to find out if a method has a Test annotation is highlighted in green. If a test method invocation throws an exception, the test is deemed to have failed, and a failure report is printed. Finally, a summary is printed showing the number of tests that passed and failed. Here is how it looks when you run the testing tool on the Foo program (above): $ java RunTests Foo Test public static void Foo.m3() failed: java.lang.RuntimeException: Boom Test public static void Foo.m7() failed: java.lang.RuntimeException: Crash Passed: 2, Failed 2
While this testing tool is clearly a toy, it demonstrates the power of annotations and could easily be extended to overcome its limitations.