Annotations in Tiger, Part 1: Add metadata to Java code

来源:互联网 发布:阿铭linux视频 编辑:程序博客网 时间:2024/05/16 08:51

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).

The value of metadata

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?

Don't miss the rest of this series

Be sure to read, "Part 2" of this series, which explores custom annotations.

Compiler checking

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.

JSR 175

JSR 175, A Metadata Facility for the Java Programming Language, provides the official justification and specification for the incorporation of metadata into the core Java language (see Resources). According to the JSR, annotations "do not directly affect the semantics of a program. Development and deployment tools can, however, read these annotations and process them in some fashion, perhaps producing additional Java programming language source files, XML documents, or other artifacts to be used in conjunction with the program containing the annotations."

Code analysis

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) Integers. 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.


Annotation basics

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?).

Annotation or annotation type?

Confused about what's an annotation versus an annotation type? The easiest way to get this straight is to think in terms of Java language concepts you're already familiar with. You can define a single class (for example, Person), and you will always have only one version of the class in the JVM (assuming you aren't doing nasty class path things). However, you might have 10 or 20 instances of that class in use at any given time. There's still only a single Person class, but it's used multiple times, in varying ways. The same is true of annotation types and annotations. An annotation type is analogous to the class, and an annotation is analogous to an instance of that class.

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
@TODO(
severity=TODO.CRITICAL,
item="Add functionality to calculate the mean of the student's grades",
assignedTo="Brett McLaughlin"
),
@TODO(
severity=TODO.IMPOTANT,
item="Print usage message to screen if no command-line flags specified",
assignedTo="Brett McLaughlin"
),
@TODO(
severity=TODO.LOW,
item="Roll a new website page with this class's new features",
assignedTo="Jason Hunter"
)
})

 

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.


The Override annotation

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;

public class OverrideTester {

public OverrideTester() { }

@Override
public String toString() {
return super.toString() + " [Override Tester Implementation]";
}

@Override
public int hashCode() {
return toString().hashCode();
}
}

 

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;

public class OverrideTester {

public OverrideTester() { }

@Override
public String toString() {
return super.toString() + " [Override Tester Implementation]";
}

@Override
public int hasCode() {
return toString().hashCode();
}
}

 

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

Missing functionality

It would be nice if Deprecated let you include an error-type message, in the single-value annotation style. The compiler could then print the message when users use the deprecated method. The message could indicate how serious the consequences of using the method are, say when the method will be phased out, and even suggest alternatives. Alas, maybe this will come with the next J2SE version ("Mustang," they're calling that one now).

This handy little feature will help you catch typos very quickly.


The Deprecated annotation

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;

public class DeprecatedClass {

@Deprecated public void doSomething() {
// some code
}

public void doSomethingElse() {
// This method presumably does what doSomething() does, but better
}
}

 

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() {
List wordList = new ArrayList(); // no typing information on the List

wordList.add("foo"); // causes error on list addition
}

 

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"})
public void nonGenericsMethod() {
List wordList = new ArrayList(); // no typing information on the List

wordList.add("foo"); // causes error on list addition
}

 

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.


Conclusion

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.

 

Resources

  • 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.
原创粉丝点击