In the first
article in this series
, I explained what metadata is, why it's valuable, and
how to use the basic built-in annotations introduced in J2SE 5.0 (aka Tiger). If
you're comfortable with these concepts now, you might already be thinking that
the three standard annotations Java 5 offers aren't especially robust. You can
do only so much with Deprecated
, SuppressWarnings
, and
Override
. Fortunately, Tiger also lets you define your own
annotation types. In this article, I'll take you through this relatively simple
process with some examples. You'll also find out how to annotate your own
annotations and what you gain by doing so. My thanks to O'Reilly Media, Inc.,
which has graciously allowed me to use the code sample from the annotations
chapter of my book on Tiger for this article (see Resources
).
Defining your own annotation type
With the addition of a little syntax (and Tiger has added plenty of
syntactical constructs), the Java language supports a new type -- the
annotation type
. An annotation type looks a lot like an ordinary class,
but it has some unique properties. Most notably, you can use it with the at sign
(@
) in your classes to annotate your other Java code. I'll walk you
through the process piece by piece.
Defining a new annotation type is a lot like creating an interface, except
that you precede the interface
keyword with the @
sign. Listing 1 shows an example of the simplest possible annotation
type:
Listing 1. A very simple annotation
type
package com.oreilly.tiger.ch06; |
Listing 1 is pretty self-explanatory. If you compile this annotation type and ensure that it's in your classpath, you can then use it on your own source code methods to indicate that a method or class is still in progress, as in Listing 2:
Listing 2. Using your custom annotation
type
@com.oreilly.tiger.ch06.InProgress |
You use the annotation type in Listing 1 exactly the same way you use the
built-in annotation types, except that you indicate the custom annotation by
both its name and package. Of course, normal Java rules apply, so you can import
the annotation type and refer to it as simply @InProgress
.
The basic usage I've just shown you is far from robust. As you'll remember from Part 1, annotation types can have member variables (see Resources ). This is useful, especially when you begin to use annotations as more-sophisticated metadata, not just raw documentation. Code-analysis tools like to have lots of information to crunch, and custom annotations can supply that information.
Data members in annotation types are set up to work using limited information. You don't define a member variable and then provide accessor and mutator methods. Instead, you define a single method, named after the member, that you want to allow for. The data type should be the return value of the method. The concrete example in Listing 3 should make this somewhat confusing requirement more clear:
Listing 3. Adding a member to
an annotation type
package com.oreilly.tiger.ch06; |
As odd as Listing 3 might look, it's what you need in annotation types.
Listing 3 defines a string named value
that the annotation type can
accept. You then use the annotation type as in Listing 4:
Listing 4. Using an annotation type with a member
value
@com.oreilly.tiger.ch06.InProgress |
Again, not much is tricky here. Listing 4 assumes that
com.oreilly.tiger.ch06.TODO
has been imported, so in the source,
you don't
prefix the annotation with its package name. Also note that
Listing 4 uses the shorthand approach: You feed the value ("Figure out the
amount of interest per month")
into the annotation without specifying the
member-variable name. Listing 4 is equivalent to Listing 5, which doesn't use
the shorthand:
Listing 5. "Longhand" version of
Listing 4
@com.oreilly.tiger.ch06.InProgress |
Of course, we're all coders, so who wants to mess with the longhand version?
Take note, though -- the shorthand version is available only if the annotation
type has a single
-member variable named value
. If you don't
meet this condition, you lose the shorthand feature.
What you've seen so far is a good start, but you have plenty of ways to
spruce it up. Probably the next addition you'll think of is to set some default
values for the annotation. This is nice when you want users to specify some
values, but they need to specify other values only if they differ from the
default. Listing 6 illustrates both this concept and its implementation with
another custom annotation -- a fuller-featured version of the TODO
annotation type from Listing 4
:
Listing 6. Annotation type with default values
package com.oreilly.tiger.ch06; |
The GroupTODO
annotation type in Listing 6 adds several new
variables. Note that this annotation type doesn't have a single-member variable,
so you gain nothing by naming one of the variables value
. Any time
you have more than one member variable, you should name them as precisely as
possible. You don't get the benefit of the shorthand syntax shown in Listing 5
, so you might as well be a little more verbose
and create better self-documentation for your annotation type.
Another new feature that Listing 6 demonstrates is that the annotation type defines its own enumeration. (Enumerations -- usually just called enums -- are another new feature of Java 5. This isn't anything remarkable, or even specific to annotation types.) Then, Listing 6 uses the new enumeration as the type for the member variable.
Finally, back to the subject at hand -- default values. Establishing them is
pretty trivial. You add the keyword default
at the end of the
member declaration, and then supply the default value. As you might expect, this
must be the same type that you declared for the member variable. Again, this
isn't rocket science -- just a little bit of a lexical twist. Listing 7 shows
the GroupTODO
annotation in action, in a case in which
severity
is not
indicated:
Listing
7. Taking advantage of default values
@com.oreilly.tiger.ch06.InProgress |
Listing 8 shows the same annotation in use, this time with a value supplied
for severity
:
Listing 8. Overriding
default values
@com.oreilly.tiger.ch06.InProgress |
Before closing the book on annotations (at least in this series), I'll deal briefly with annotating annotations. The set of predefined annotation types you learned about in Part 1 have a predetermined purpose. However, as you move into writing your own annotation types, the purpose of your annotation types isn't always self-evident. In addition to basic documentation, you'll probably write types that are specific to a certain member type, or perhaps a certain set of member types. This requires you to supply some sort of metadata on your annotation type, so that the compiler can enforce the annotation's intended functionality.
Of course, annotations -- the Java language's choice for metadata -- should immediately come to mind as the solution. You can use four predefined annotation types -- referred to as meta-annotations -- to annotate your annotations. I'll cover each one in turn.
The most obvious meta-annotation is one that allows you to indicate which
program elements can have annotations of the defined type. Unsurprisingly, this
meta-annotation is called Target
. Before you see how to use
Target
, though, you need to know about another new class --
actually an enum -- called ElementType
. This enum defines the
various program elements that an annotation type can target. Listing 9 show the
ElementType
enum in its entirety:
Listing
9. The ElementType enum
package java.lang.annotation; |
The enumerated values in Listing 9 are pretty obvious, and you can figure out
on your own (with help from the comments) how each one applies. When you use the
Target
meta-annotation, you supply it at least one of these
enumerated values and indicate which program elements the annotated annotation
can target. Listing 10 shows Target
in action:
Listing 10. Using the Target meta-annotation
package com.oreilly.tiger.ch06; |
Now the Java compiler will apply TODO
only to types, methods,
constructors, and other annotation types. This helps you ensure that nobody else
takes your annotation type and misapplies it (or, better yet, that you
don't misapply it in a fit of fatigue).
The next meta-annotation you want to get under your fingers is
Retention
. This meta-annotation is related to how the Java compiler
treats the annotated annotation type. The compiler has several options:
- Retain the annotation in the compiled class file of the annotated class, and read it when the class first loads
- Retain the annotation in the compiled class file, but ignore it at runtime
- Use the annotation as indicated, but then discard it in the compiled class file
These three options are represented in the
java.lang.annotation.RetentionPolicy
enum, shown in Listing
11:
Listing 11. The RetentionPolicy enum
package java.lang.annotation; |
As you should expect by now, the Retention
meta-annotation type
takes as its single argument one of the enumerated values you see in Listing 11.
You target this meta-annotation to your annotations, as shown in Listing
12:
Listing 12. Using the Retention
meta-annotation
@Retention(RetentionPolicy.SOURCE) |
As you can tell from Listing 12, you can use the shorthand form here, because
Retention
has a single-member variable. And if you want the
retention to be RetentionPolicy.CLASS
, you don't have to do a
thing, because that's the default behavior.
The next meta-annotation is Documented
. This is another one
that's pretty easy to understand, partly because Documented
is a
marker annotation. As you should remember from Part 1, marker annotations have
no member variables. Documented
indicates that an annotation should
appear in the Javadoc for a class. By default, annotations are not
included in Javadoc -- a fact worth remembering when you spend a lot of time
annotating a class, detailing what's left to do, what it does correctly, and
otherwise documenting its behavior.
Listing 13 shows what the Documented
meta-annotation looks like
in use:
Listing 13. Using the Documented
meta-annotation
package com.oreilly.tiger.ch06; |
The one "gotcha" with Documented
is in the retention policy.
Notice that Listing 13 specifies the annotation's retention as
RUNTIME
. This is a required
aspect of using the
Documented
annotation type. Javadoc loads its information from
class files (not source files), using a virtual machine. The only way to ensure
that this VM gets the information for producing Javadoc from these class files
is to specify the retention of RetentionPolicy.RUNTIME
. As a
result, the annotation is kept in the compiled class file and
is loaded
by the VM; Javadoc then picks it up and adds it to the class's HTML
documentation.
The final meta-annotation, Inherited
, is probably the most
complicated to demonstrate, the least-often used, and the one that creates the
most confusion. All that said, let's cheerily run through it.
First, take a use case: Suppose that you mark a class as being in progress,
through your own custom InProgress
annotation. No problem, right?
This will even show up in the Javadoc if you've correctly applied the
Documented
meta-annotation. Now, suppose you write a new class and
extend the in-progress class. Easy enough, right? But remember that the
superclass is in progress. If you use the subclass, and even look at its
documentation, you get no indication that anything is incomplete. You would
expect to see that the InProgress
annotation is carried through to
subclasses -- that it's inherited
-- but it isn't. You must use the
Inherited
meta-annotation to specify the behavior you want, as
shown in Listing 14:
Listing 14. Using the Inherited
meta-annotation
package com.oreilly.tiger.ch06; |
With the addition of @Inherited
, you'll see the
InProgress
annotation show up on subclasses of annotated classes.
Of course, you don't really want this behavior on all your annotation types
(that's why the default is not
to inherit); for example, the
TODO
annotation wouldn't (and shouldn't) be propagated. Still, for
the case I've shown here, Inherited
can be quite helpful.
At this point, you're ready to go back into the Java world and document and
annotate everything. Then again, this reminds me a bit of what happened when
everyone figured out Javadoc. We all went into the mode of over-documenting
everything, before someone realized that Javadoc is best used for clarification
of confusing classes or methods. Nobody looks at those easy-to-understand
getXXX()
and setXXX()
methods you worked so hard to
Javadoc.
The same trend will probably occur with annotations, albeit to a lesser degree. It's a great idea to use the standard annotation types often, and even heavily. Every Java 5 compiler will support them, and their behavior is well-understood. However, as you get into custom annotations and meta-annotations, it becomes harder to ensure that the types you work so hard to create have any meaning outside of your own development context. So be measured. Use annotations when it makes sense to, but don't get ridiculous. However you use it, an annotation facility is nice to have and can really help out in your development process.
- Don't miss Part 1
of this
series, which introduces annotations in Java 5.0.
- The open source XDoclet
code-generation engine enables attribute-oriented programming for the Java
language.
- JSR 175
, the
specification for incorporating a metadata facility into the Java language, is
in proposed final draft status under the Java Community Process.
- Visit Sun's home base for all things J2SE
5.0.
.
- You can download
Tiger
and try it out for yourself.
- John Zukowski's series Taming
Tiger
covers the new features of Java 5.0 in practical tip-based
format.
- Java 1.5 Tiger: A
Developer's Notebook
(O'Reilly & Associates; 2004), by Brett
McLaughlin and David Flanagan, covers almost all of Tiger's newest features --
including annotations -- in a code-centric, developer-friendly format.
- Find hundreds more Java technology resources on the developerWorks
Java
technology zone
.
- Browse for books on these and other technical topics.