Java Reflection Tutorial, Part 2: Annotations

Welcome to Part 2 of the Java reflection tutorial. This part will focus on annotations, which allow arbitrary data to be attached to a class, method, field, or any other symbol.

If you have not yet read Part 1, please go read it. It explains some basic concepts.

Now, without further ado…

Getting Started

This tutorial assumes that you have Basic.java from part 1. If not (I don’t), here it is:

import java.lang.reflect.*;
import java.lang.annotation.*;

public class Basic {
    public static void main(String[] args) {
        // SETUP //
        System.out.println("Hello Java Reflection");

        // GETTING THE NAME OF A CLASS //
        Class<?> cls = Basic.class;
        System.out.println("The class's name is " + cls.getName());
        
        // GETTING THE NAME OF A METHOD //
        Basic basic = new Basic();
        basic.doSomething("whatever");
        try {
            Method doSomething = cls.getMethod("doSomething", String.class);
            System.out.println("The method's name is " + doSomething.getName());
        } catch(NoSuchMethodException e) {
            System.out.println("A whatsit happened: " + e.toString());
        }
    }
    public void doSomething(String whatever) {
        System.out.println("Doing something very interesting...");
        System.out.println("Whatever is " + whatever);
    }
}

Now, let’s take a look at annotations.

Built-In Annotations

Java has several built-in annotations, including @Override, @Deprecated, and others. Let’s take a look at @Override first.

@Override allows you to override previously defined methods. For instance, if you had a class called Class1 (very creative name, isn’t it?):

public class Class1 {
    public void doSomething() {
        System.out.println("Hello world, Class1 style!");
    }
}

Then, say that you had a class called Class2, which extended Class1, and you wanted to define anew the doSomething method. You might take a gander at it like this:

public class Class2 extends Class1 {
    public void doSomething() {
        System.out.println("Hello world, Class2 style!");
    }
}

However, you would be gandering at hot air, and the compiler would scream loudly at you for it. Don’t do that. In Java, you need to annotate any overridden method with @Override, like so:

public class Class2 extends Class1 {
    @Override
    public void doSomething() {
        System.out.println("Hello world, Class2 style!");
    }
}

As you can see, using an annotation is very easy: Just smack it down in front of the thing you want to annotate, and you’re off to the races.

And now, you can go and enjoy your polymorphic “Hello, World” in peace. But don’t go away just yet, because there’s more coming. Unless, of course, you’re bored out of your mind, in which case you would probably prefer some of the other content on this site.

An important note: @Override is not needed when overriding methods defined in interfaces or abstract classes. Those are meant to be overridden, and so the all-knowing Oracle realized that it was redundant to mandate that people specifically denote that they’re overriding something which cannot be used in any way except being overridden.

Anyhoo, now we move on to @Deprecated.

(looks up what @Deprecated actually does)

@Deprecated marks a program element (e.g. package, class, field, method, etc.) as deprecated. If you try to use a deprecated field, the compiler will give you a warning.

To use it, just put it in front of just about anything, like @Override:

@Deprecated
public class DontUseMe {
    @Deprecated
    public void blowUpTheWorld() {}
    
    @Deprecated
    public String goodbyeWorld = "Goodbye, World";
}

Just in case it wasn’t obvious, now people know not to use that class.

This is just a small sample of the built-in annotations that Java provides. To see the full list (as of Java 8), see https://docs.oracle.com/javase/tutorial/java/annotations/predefined.html.

Creating Your Own Annotations

To create your own annotations, use an @interface block. Not an interface, an @interface. Each parameter for the annotation is defined as a method in the @interface. For example, to create an annotation like the Author annotation in the official tutorial:

@interface Author {
    String name();
    String date();
}

And there you have it, a simple annotation. To use it, make sure it’s imported, and:

@Author(name = "Marty", date = "The Future")
// declare something here

Tip
If an annotation has only one method named value, you can pass it as an unnamed argument. For example, if @Author didn’t have date, and you renamed name to value, you could use:
@Author("Nobody In Particular")

Of course, you would probably want to make date into a Date, so that you don’t trip over unexpected time-traveling DeLoreans, but this suffices for a demonstration.

Now, this annotation can be used on everything at the moment — even the type of an argument. Since it’s kind of silly to put an @Author annotation on the type String, let’s fix that.

To restrict where your annotation can be used, you can annotate it (yes, you can put annotations on annotations) with a @Target annotation, in java.lang.annotation. @Target takes one argument, an ElementType or an array of ElementTypes. ElementType is an enum with the following options (mostly copy-pasted from https://docs.oracle.com/javase/tutorial/java/annotations/predefined.html):

  • ElementType.ANNOTATION_TYPE can be applied to an annotation type.
  • ElementType.CONSTRUCTOR can be applied to a constructor.
  • ElementType.FIELD can be applied to a field or property.
  • ElementType.LOCAL_VARIABLE can be applied to a local variable.
  • ElementType.METHOD can be applied to a method-level annotation.
  • ElementType.PACKAGE can be applied to a package declaration.
  • ElementType.PARAMETER can be applied to the parameters of a method.
  • ElementType.TYPE can be applied to any type declaration.

For example, to restrict @Author to types, annotations, constructors, fields, packages, and methods (the sensible items), you would define it like so:

import java.lang.annotation.*;
// ...
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.PACKAGE, ElementType.METHOD})
@interface Author {
    String name();
    String date();
}

Now, if you try to, say, take undo credit for the creation of java.lang.String, the compiler will yell at you.

Using Annotations for Reflection

Now, we get to the part that justifies this post’s inclusion in a series entitled “Java Reflection Tutorial”. To use annotations for reflection, you need to use the Annotation interface (in the java.lang.annotation package, which should already be imported from Basic.java). To get started, let’s put Author into its own file. Call it Author.java, and put it in the same package as Basic.java:

import java.lang.annotation.*;

@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.PACKAGE, ElementType.METHOD})
public @interface Author {
    String name();
    String date();
}

If it’s in a named package, be sure to put a package declaration!

Now, let’s put an @Author annotation on Basic, to let the world know who wrote it. Put this on a line before the class declaration in Basic.java:

@Author(name = "Fozzie Bear", date = "Somewhere around 2021")

Now that the humans know, let’s make sure that the computers know as well. To get the Annotation instance for the annotation, we can use the Class.getDeclaredAnnotationsByType method. Since we know that we can only have one instance of the annotation on any given element (to repeat annotations, annotate the annotation declaration with @Repeatable), we can just take the first element of the returned list and cast it to Author: (put this at the end of main)

Author author = cls.getDeclaredAnnotationsByType(Author.class)[0];

However, if you try to run this (I did), you’ll find that the annotation doesn’t exist! This is because of retention policies.

Somewhat Unplanned Interlude: Retention Policies

Retention policies (defined using the @Retention annotation) tell the compiler how long to keep the annotation around for. A value of RetentionPolicy.CLASS (the default) tells the compiler to keep the annotation around during compilation, but ignore it at runtime. A value of RetentionPolicy.SOURCE tells the compiler to ignore the annotation altogether, and makes the annotation behave, for all intents and purposes, like a comment. A value of RetentionPolicy.RUNTIME, which is what we want, tells the compiler to keep the annotation around during both compile and runtime, for reflection purposes. To make @Author available for reflection, add a retention policy in Author.java:

// ...
@Retention(RetentionPolicy.RUNTIME)
// ...

And Now, We Return To Our Regularly Scheduled Programming

At this point, we’re retrieving the annotation class, but we’re not actually doing with it. To finish up, let’s print out the author and date from the annotation:

System.out.println("This class was written by " + author.name());
System.out.println("Date: " + author.date());

Now, if you run it, along with the output it previously gave, it will also print out the following:

This class was written by Fozzie Bear
Date: Somewhere around 2021

And we’re done! This tutorial just scratches the surface of what’s possible with annotations, though, so be sure to check out the official tutorial at https://docs.oracle.com/javase/tutorial/java/annotations/index.html.

An Exercise

Try making Author able to be used multiple times, and modify Basic.java to print out all of the occurrences. For instance:

@Author(name = "Fozzie Bear", date = "2021-09-08")
@Author(name = "Kermit", date = "2021-12-21")

And the output would be:

This class was written by Fozzie Bear
Date: 2021-09-08
This class was written by Kermit
Date: 2021-12-21

One thought on “Java Reflection Tutorial, Part 2: Annotations

  1. This work is most impressive, Aleks! Kudos to your programming talents and those of your team. John Miller (grandfather of Charlotte, The Amazing, Ruley)

    On Sat, Feb 26, 2022 at 3:15 PM FTC 16221 Manchester Machine Makers wrote:

    > Aleks Rutins posted: ” Welcome to Part 2 of the Java reflection tutorial. > This part will focus on annotations, which allow arbitrary data to be > attached to a class, method, field, or any other symbol. If you have not > yet read Part 1, please go read it. It explains some basi” >

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s