Tuesday, February 17, 2009

what is an annotation? (Java Tiger Edition)

Defined as part of JSR 175: A Metadata Facility for the Java Programming Language,
annotations offer a way to associate metadata with program elements
(such as classes, interfaces, and methods). They can be thought of as
additional modifiers without changing the generated bytecode for those
elements.

The concept of introducing metadata to source code isn't new with
J2SE 5.0. You can add an @deprecated tag to a method's javadoc comments
and the compiler treats this as metadata about the method. This ability
has been in place since the 1.0 release of J2SE. The initial release of
the platform already had deprecated methods, with the getenv() method
of System (though this wasn't in the Java Language
Specification until the 1.1 addendum). The concept is almost the same
now, at least the @ part of the syntax. Only the location has changed
-- an annotation tag goes in source, not comments. The main point here
is that annotations are a systematic way to support a declarative
programming model.



This leads to the first annotation that comes with J2SE 5.0:
@Deprecated. Notice the capital D here. Functionally, @Deprecated in
source works the same as @deprecated in the javadoc associated with a
class or method. By flagging methods with the @Deprecated tag, you're
alerting the compiler to warn the user when the method or class is used.



The following Main class has a method named deprecatedMethod() that
is flagged with the @Deprecated annotation and a, @deprecated comment:














   public class Main {

   

     /**

      @deprecated Out of date. Use System.foobar() instead.

     */ 



     @Deprecated

     public static void deprecatedMethod() {

       System.out.println("Don't call me");

     }

   }









You compile a class with annotations the same way as you do for one without annotations:



   > javac Main.java


As expected, this produces Main.class.



If you use the deprecated method, it produces a compilation-time
warning -- just like using the @deprecated tag in a javadoc. Here's an
example:













   public class User {

     public static void main(String args[]) {

       Main.deprecatedMethod();

     }

   }







Compile the class:



   > javac User.java


and you'll see the following warning about using a deprecated method:



   Note: User.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.


Adding the -Xlint to the compilation line shows specifically what is wrong:



   > javac -Xlint:deprecation User.java

User.java:3: warning: [deprecation] deprecatedMethod() in
Main has been deprecated
Main.deprecatedMethod();
^
1 warning


The change from the @deprecated comment to the @Deprecated
annotation doesn't introduce anything really new to the system. It only
slightly changes the way of doing the same thing. The other two new
annotations available to the J2SE 5.0 platform, @Override and
@SuppressWarnings, do introduce new functionality to the platform.



The @Override annotation can be used with method declarations. As
its name implies, you use the @Override annotation to flag a method
that is supposed to override a method of the superclass. Why use it? To
catch errors sooner. How many times have you meant to override a
method, but either misspelled the method name, specified the wrong
arguments, or had a different return type? In other words, how often
have you defined a new method when what you really wanted to do was
override an existing one? By using @Override, you'll find the problem
in the following class sooner rather than later:













   public class Overrode {

     @Override

     public int hashcode() {

       return 0;

     }

     

     @Override

     public boolean equals(Object o) {

       return true;

     

   }







The problem here is that the method name should be hashCode, not
hashcode. Suppose the method declaration is buried in the source for a
much larger class definition. Without the first @Override annotation,
how long would it take you to realize that your hashCode() method (with
camel-case for spelling, not all lowercase) is not being called, and
you're getting the default behavior of the parent Object class? Thanks
to the @Override annotation, compiling the class produces a compile
time error, alerting you to the problem:



   > javac Overrode.java

Overrode.java:2: method does not override a method from its
superclass
@Override
^
1 error


The sooner you can find errors of this nature, the cost of
correction becomes drastically reduced. Note that the hashCode() method
should never return a constant. For a fuller description of the proper
usage of hashCode() and equals(), see Item 8 in the book Effective Java Programming Language Guide by Joshua Bloch.



The final of the three new annotations in J2SE 5.0,
@SuppressWarnings, is the most interesting. It tells the compiler not
to warn you about something that it would normally warn you about.
Warnings belong to a category, so you have to tell the annotation what
types of warnings to suppress. The javac compiler defines seven options
to suppress: all, deprecation, unchecked, fallthrough, path, serial,
and finally. (The language specification defines only two such types:
deprecation and unchecked.)



To demonstrate, let's look at the suppression of the fallthrough
option. Let's start with the following class. Notice that the class is
missing a break statement for each case of the switch statement:













   public class Fall {

     public static void main(String args[]) {

       int i = args.length;

       switch (i) {

         case 0:  System.out.println("0");

         case 1:  System.out.println("1");

         case 2:  System.out.println("2");

         case 3:  System.out.println("3");

         default: System.out.println("Default");



       }

     }

   







Compile the class with javac. You'll see that it simply creates the .class file, and displays no warnings:



   javac Fall.java 


If you want the compiler to warn you about switch statements that
fall through (that is, one or more break statements are missing), you
compile with the -Xlint:fallthrough option:



   javac -Xlint:fallthrough Fall.java 


This produces the following warnings:



   Fall.java:6: warning: [fallthrough] possible fall-through 
into case
case 1: System.out.println("1");
^
Fall.java:7: warning: [fallthrough] possible fall-through
into case
case 2: System.out.println("2");
^
Fall.java:8: warning: [fallthrough] possible fall-through
into case
case 3: System.out.println("3");
^
Fall.java:9: warning: [fallthrough] possible fall-through
into case
default : System.out.println("Default");
^
4 warnings


But what if you want to ignore the fact that the switch statement is
missing a break statement for each case? That's where the
@SuppressWarnings annotation comes into play. If you add the following
line before the main() method declaration:



   @SuppressWarnings("fallthrough")


Compiling the class with the -Xlint:fallthrough option:



   javac -Xlint:fallthrough Fall.java 


will just generate the .class file and display no warnings.



@SuppressWarnings annotations can also be used to suppress other
warnings such as those that would be displayed if you used a collection
without specifying the data type of the collection elements. Don't use
the @SuppressWarnings annotation simply to avoid the compilation-time
warning. Use it where an unchecked warning is unavoidable, such as when
using a library that isn't built with generics in mind.



That's really about it for the built-in annotations. However one
additional thing to note, annotations (with any arguments) are
typically specified on a line by themselves.



There is much more that can be done when you define your own
annotations, rather than using the ones already defined in J2SE 5.0.
For information on defining annotations see Annotationson http://java.sun.com

No comments:

Post a Comment