One of the latest trends in programming, particularly in Java programming, is the use of metadata . Metadata, simply put, is data about data. Metadata can be used to create documentation, to track down dependencies in code, and even to perform rudimentary compile-time checking. A rash of tools for metadata, such as XDoclet (see Resources ), add these features to the core Java language and have been part of the Java programming landscape for a while.
Until the availability of J2SE 5.0 (aka Tiger, now in its second beta release), the core Java language came closest to a metadata facility with the Javadoc methodology. You use a special set of tags to mark up your code and then execute the javadoc
command to turn the tags into a formatted HTML page that documents the classes the tags are attached to. Javadoc is an inadequate metadata tool, though, because you have no solid, practical, standardized way to get at the data for any purpose other than generating documentation. The fact that HTML code is often mixed into the Javadoc output decreases its value for any other purpose even further.
Tiger incorporates a far more versatile metadata facility into the core Java language through a new feature called annotations . Annotations are modifiers you can add to your code and apply to package declarations, type declarations, constructors, methods, fields, parameters, and variables. Tiger includes built-in annotations and also supports custom annotations you can write yourself. This article will give you an overview of metadata's benefits and introduce you to Tiger's built-in annotations. Part 2 in this article series will explore custom annotations. 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 ).
In general, metadata's benefits fall into three categories: documentation, compiler checking, and code analysis. Code-level documentation is the most-often-cited use. Metadata provides a helpful way to indicate if methods are dependent on other methods, if they are incomplete, if a certain class must reference another class, and so on. This is indeed useful, but documentation is probably the least relevant reason for adding metadata to the Java language. Javadoc already provides a fairly easy-to-understand and robust way to document code. Besides, who wants to write a documentation tool when one already exists and works fine for the most part?
A more significant advantage of metadata is the ability for a compiler to use it to perform some basic compile-time checking. For example, you'll see in The Override annotation , later in this article, that Tiger introduces an annotation that lets you specify that a method overrides another method from a superclass. The Java compiler can ensure that the behavior you indicate in your metadata actually happens at a code level. This might seem silly if you've never chased down this type of bug, but most grizzled Java programming veterans have spent at least a few long nights trying to discover why their code doesn't work. When you finally realize that a method has a parameter wrong, and in fact isn't overriding a method from a superclass, much bitterness can result. A tool that consumes metadata can help you discover this type of error easily and save those nights for long-running Halo tournaments.
Arguably, the nicest feature of any good annotation or metadata tool is the ability to use the extra data to analyze code. In a simple case, you might build up a code catalog, provide required input types, and indicate return types. But -- you're probably thinking -- Java reflection offers the same benefits; after all, you can introspect code for all of this information. That might seem true on the surface, but it doesn't always hold in practice. Many times, a method accepts as input or returns as output a type that is actually not what the method wants. For example, the parameter type might be Object
, but the method works only with (again, as an example) Integer
s. This can happen easily in cases in which methods are being overridden and the superclass declares the method with generic parameters, or in a system where lots of serialization is going on. In both cases, metadata can instruct a code-analysis tool that although the parameter type is Object
, an Integer
is what's really desired. This sort of analysis is incredibly useful, and its value can't be overstated.
In more-complex cases, code-analysis tools can perform all sorts of extra tasks. The example du jour is Enterprise JavaBean (EJB) components. The dependencies and complexities in even simple EJB systems are almost staggering. You've got home and remote interfaces, along with the possibility of local and local home interfaces, as well as an implementation class. Keeping these classes all in sync is a royal pain. Metadata, though, can provide a solution for this problem. A good tool (again, XDoclet is worth mentioning here) can manage all of these dependencies, ensuring that classes that have no "code-level" connection, but do have a "logic-level" tie-in, stay in sync. It's here that metadata can truly shine.
Now that you have a grasp of the things metadata is good for, I'll introduce you to annotations in Tiger. Annotations take the form of an "at" sign (@
), followed by the annotation name. Then, you supply data to the annotation -- when data is required -- in name=value
pairs. Each time you use this sort of notation, you're making an annotation. One piece of code might have 10, 50, or more annotations. However, you'll find that several of your annotations might all use the same annotation type . The type is the actual construct used, and the annotation itself is the specific usage of that type, in a particular context (see the sidebar, Annotation or annotation type? ).
Annotations fall into three basic categories:
- Marker annotations have no variables. The annotation simply appears, identified by name, with no additional data supplied. For example,
@MarkerAnnotation
is a marker annotation. It includes no data, just the annotation name.
- Single-value annotations are similar to markers, but provide a single piece of data. Because only a single bit of data is supplied, you can use a shortcut syntax (assuming the annotation type is defined to accept this syntax):
@SingleValueAnnotation("my data")
. This should look a lot like a normal Java method call, aside from the@
sign.
- Full annotations have multiple data members. As a result, you must use a fuller syntax (and the annotation doesn't look quite so much like a normal Java method anymore):
@FullAnnotation(var1="data value 1", var2="data value 2", var3="data value 3")
.
In addition to supplying values to annotations through the default syntax, you can use name-value pairs when you need to pass in more than one value. You can also supply arrays of values to annotation variables, through curly braces. Listing 1 shows an example an array of values in an annotation.
Listing 1. Using arrayed values in annotations
@TODOItems({ // Curly braces indicate an array of values is being supplied |
The example in Listing 1 is simpler than it might look at first glance. The TODOItems
annotation type has a single variable that takes a value. The value supplied here is fairly complex, but the use of TODOItems
actually matches the single-value annotation style -- except that the single value is an array. The array contains three TODO
annotations, each of which is multivalued. Commas separate the values within each annotation, as well as the value within a single array. Easy enough, right?
But I'm getting ahead of myself a bit. TODOItems
and TODO
are custom annotations , the topic of Part 2 of this series. But I wanted you to see that even a complex annotation -- and Listing 1 is almost as complex as you can make any annotation -- isn't all that daunting. When it comes to the Java language's standard annotation types, you'll rarely see anything so convoluted. As you'll learn in the next three sections, Tiger's basic annotation types are extremely simple to use.
Tiger's first built-in annotation type is Override
. Override
should be used only on methods (not on classes, package declarations, or other constructs). It indicates that the annotated method is overriding a method in a superclass. Listing 2 shows a simple example.
Listing 2. The Override annotation in action
package com.oreilly.tiger.ch06; |
Listing 2 should be pretty easy to follow. The @Override
annotation annotates two methods -- toString()
and hashCode()
-- to indicate that they override versions of the methods from the OverrideTester
class's superclass (java.lang.Object
). This might seem trivial at first, but it's actually a nice feature. You literally cannot compile this class without overriding these methods. The annotation also ensures that when you mess with toString()
, you also have at least some indication that you should make sure that hashCode()
still matches up.
This annotation type really shines when you're up too late coding and mistype something, as in Listing 3.
Listing 3. Letting the Override annotation catch typos
package com.oreilly.tiger.ch06; |
In Listing 3, hashCode()
is mistyped as hasCode()
. The annotation indicates that hasCode()
should override a method. But in compilation, javac
will realize that the superclass (again, java.lang.Object
) has no method named hasCode()
to override. As a result, the compiler gives you an error, like the one shown in Figure 1.
Figure 1. Compiler warning from Override annotation
This handy little feature will help you catch typos very quickly.
The next standard annotation type is Deprecated
. Like Override
, Deprecated
is a marker annotation. As you might expect, you use Deprecated
to annotate a method that shouldn't be used anymore. Unlike Override
, Deprecated
should be placed on the same line as the method being deprecated (why? I'm honestly not sure), as in Listing 4.
Listing 4. Using the Deprecated annotation
package com.oreilly.tiger.ch06; |
You shouldn't expect anything unusual to happen when you compile this class on its own. But if you then use the deprecated method, either by overriding it or invoking it, the compiler processes the annotation, realizes that the method shouldn't be used, and issues an error message, as in Figure 2.
Figure 2. Compiler warning from a Deprecated annotation
Note that you need to turn on the compiler warnings, just as you must to indicate to the Java compiler that you want normal deprecation warnings. You can use one of two flags with the javac
command: -deprecated
or the new -Xlint:deprecated
flag.
The SuppressWarnings annotation
The last annotation type that you get "for free" with Tiger is SuppressWarnings
. You shouldn't have any trouble figuring out what this one does, but it's not always obvious why this annotation type is so important. It's actually a side-effect of Tiger's all-new set of features. For example, consider generics; generics make all sorts of new type-safe operations possible, especially when it comes to Java collections. However, because of generics, the compiler now throws warnings when collections are used without type safety. That's helpful for code aimed at Tiger, but it makes writing code intended for Java 1.4.x or earlier a real pain. You'll constantly receive warnings about things that you're not at all concerned about. How can you get the compiler to leave you in peace?
SupressWarnings
comes to the rescue. SupressWarnings
, unlike Override
and Deprecated
, does have a variable -- so you use the single-annotation style for working with it. You can supply the variable as an array of values, each of which indicates a specific type of warning to suppress. Take a look at the example in Listing 5, which is some code that normally generates an error in Tiger.
Listing 5. Tiger code that isn't type-safe
public void nonGenericsMethod() { |
Figure 3 shows the result of compiling the code in Listing 5.
Figure 3. Compiler warning from non-typed code
Listing 6 gets rid of this pesky warning by employing the SuppressWarnings
annotation.
Listing 6. Suppressing a warning
@SuppressWarnings(value={"unchecked"}) |
Simple enough, right? Just locate the type of warning (appearing in Figure 3 as "unchecked"), and pass it in to SuppressWarnings
.
The fact that the value of the variable in SuppressWarnings
takes an array lets you suppress multiple warnings in the same annotation. For example, @SuppressWarnings(value={"unchecked", "fallthrough"})
takes a two-value array. This facility provides a pretty flexible means of handling errors without requiring you to be overly verbose.
Although the syntax you've seen here might be new, you should be thinking that annotations are pretty easy to understand and use. That said, the standard annotation types that come with Tiger are rather bare-boned and leave much to be desired. Metadata is increasingly useful, and you'll certainly come up with annotation types that are perfect for your own applications. In Part 2 of this series. I'll detail Tiger's support for writing your own annotation types. You'll learn how to create a Java class and define it as an annotation type, how to let the compiler know about your annotation type, and how to use it to annotate your code. I'll even dig a bit into the bizarre-sounding but useful task of annotating annotations. You'll quickly master this new construct in Tiger.
- Don't miss "Annotations in Tiger, Part 2 ," the second part of this series, which explores custom annotations.
- 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.