Declarative Programming in Java
Pages: 1, 2, 3
Meta-Annotations
Java defines several standard meta-annotations (annotation types designed for annotating annotation type declarations).
The new package java.lang.annotation contains the following meta-annotations:
| Meta-annotation | Purpose |
|---|---|
@Documented |
Indicates that annotations with a type are to be documented by javadoc and similar tools by default. |
@Inherited
|
Indicates that an annotation type is automatically inherited. |
@Retention |
Indicates how long annotations with the annotated type are to be retained.
Example: @Retention(RetentionPolicy.RUNTIME)
The enumeration RetentionPolicy defines the constants to be used for specifying Retention. |
@Target |
Indicates the kinds of program element to which an annotation type is applicable.
Example: @Target({ElementType.FIELD, ElementType.METHOD})
The enumeration ElementType defines constants that are used with the Target meta-annotation type to specify where it is legal to use an annotation type. |
Standard Annotations
There are two standard annotations in the java.lang package.
| Annotation | Purpose |
|---|---|
@Deprecated |
Reincarnation of deprecated javadoc tag as an annotation in JDK 1.5. Compilers warn when a deprecated program element is used or overridden in non-deprecated code. |
@Overrides |
Indicates that a method declaration is intended to override a method declaration in a super-class. If a method is annotated with this annotation type but does not override a super-class method, compilers are required to generate an error message. |
The following example illustrates the use of standard annotations.
public class Parent {
@Deprecated
public void foo(int x) {
System.out.println("Parent.foo(int x) called.");
}
}
public class Child extends Parent {
@Overrides
public void foo() {
System.out.println("Child.foo() called.");
}
}
My intention is to extend the Parent class and override the foo(int x) method in Child class. By mistake, the foo method in the child does not override the one in the parent, because of the mismatch in the signature. Obviously, this is a bug. In the past, this kind of bug could be identified only at runtime after hours of debugging. Now, the use of the @Overrides annotation can save hours wasted in debugging. When a method is annotated with @Overrides, the compiler will check whether the method in a child class really overrides a method in the parent. The compiler will report an error when no method is overridden.
javac -source 1.5 Parent.java Child.java
Child.java:3: method does not override
a method from its superclass
@Overrides
^
1 error
Let's correct the error by modifying the foo method signature, and compile the code again.
public class Child extends Parent{
@Overrides
public void foo(int x) {
System.out.println("Child.foo(int x) called.");
}
}
javac -Xlint:deprecation -source 1.5 Child.java
Child.java:4: warning:
[deprecation] foo(int) in Parent has been
deprecated
public void foo(int x) {
^
1 warning
The foo method is marked as deprecated using the @Deprecated annotation, so the compiler reports a warning, as shown above.
A Complex Example
To keep the introduction simple, I used a single valued annotation. Now it's time to look at a complex one.
public @interface Trademark {
String description();
String owner();
}
In the code snippet above we have declared an annotation called Trademark with two members: description and owner, both of which return String.
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.PACKAGE})
public @interface License {
String name();
String notice();
boolean redistributable();
Trademark[] trademarks();
}
In the code snippet above, we have declared an annotation called License, which has members that return String, boolean, and an array of Trademark annotations. Now we can use the License annotation.
@License(
name = "Apache",
notice = "license notice ........",
redistributable = true,
trademarks =
{@Trademark(description="abcd",owner="xyz"),
@Trademark(description="efgh",owner="klmn")}
)
public class Example2 {
public static void main(String []args) {
License lic;
lic=Example2.class.getAnnotation(License.class);
System.out.println(lic.name());
System.out.println(lic.notice());
System.out.println(lic.redistributable());
Trademark [] tms = lic.trademarks();
for(Trademark tm : tms) {
System.out.println(tm.description());
System.out.println(tm.owner());
}
}
}
The above example shows how to use annotations that have multiple members with String, boolean, and array return types. It also illustrates how to define annotations with parameter values that are annotations. The main method in Example2 illustrates how to access the annotations at runtime.
Defaults and Order of name=value Pairs
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Help {
String topic() default "Unknown Topic";
String url();
}
Default values for members can be specified in the annotation declaration so that it becomes optional when defining it. In the above example, String topic() default "Unknown Topic"; declares a default value for topic. While defining the Help annotation, the topic is optional and need not be specified. For example, @Help(url=".../help.html") is valid. The url must be specified, otherwise this will result in a compilation error.
In the annotation definition, the name=value pairs can be specified in any order.
@Help(topic="Order does not matter", url=".../help.html") and @Help(url=".../help.html", topic="Order does not matter") are considered the same.