Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Annotations

An annotation is a marker in code that starts with @: @Override, @Test, @Deprecated. On its own it does nothing — it's extra information (metadata) for the compiler, build tools and libraries. For example, the JUnit library finds test methods by the @Test annotation, and Spring configures an application through its own annotations.

@Override

The most common annotation is @Override. It tells the compiler: "this method overrides a method of the parent class or interface". If that isn't true, the compiler reports an error:

class Animal {
    String sound() {
        return "...";
    }
}
 
class Cat extends Animal {
    @Override
    String sound() {
        return "Meow";
    }
}
 
void main() {
    Animal pet = new Cat();
    IO.println(pet.sound());
}

Output:

Meow

The code works without @Override too. But the annotation protects against typos: if you write @Override String sounds(), the program won't compile.

class Animal {
    String sound() {
        return "...";
    }
}
 
class Cat extends Animal {
    @Override
    String sounds() {
        return "Meow";
    }
}
 
void main() {
    IO.println(new Cat().sound());
}

Output:

Main.java:8: error: method does not override or implement a method from a supertype
    @Override
    ^
1 error
error: compilation failed

Other built-in annotations

  • @Deprecated — the element is outdated and shouldn't be used. The compiler warns everyone who calls it.
  • @SuppressWarnings("unchecked") — asks the compiler not to print a particular warning.
  • @FunctionalInterface — the interface must have exactly one abstract method so that a lambda can implement it. Add a second one and the compiler reports an error.
@FunctionalInterface
interface Discount {
    double apply(double price);
}
 
void main() {
    Discount blackFriday = price -> price * 0.7;
    IO.println(blackFriday.apply(100));
}

Output:

70.0

Your own annotation

An annotation is declared with @interface. Inside you list its elements — they are written like methods without parameters and can have default values:

// Command.java
import java.lang.annotation.*;
 
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Command {
    String name();
    String description() default "no description";
}

The two annotations above the declaration are meta-annotations, that is, annotations for annotations:

  • @Retention sets how long the annotation is kept. RUNTIME means it's available while the program runs. Without it only the compiler sees it.
  • @Target sets what can be annotated: METHOD — only methods, TYPE — classes, FIELD — fields.

Reading annotations

Annotations with RUNTIME can be read through reflection — a mechanism that lets a program examine its own classes and methods. It's covered in detail in the next lesson. Let's find all command methods and print their descriptions:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Command {
    String name();
    String description() default "no description";
}
 
class Bot {
    @Command(name = "/start", description = "greeting")
    void start() {}
 
    @Command(name = "/help")
    void help() {}
 
    void internal() {}
}
 
void main() {
    List<Method> methods = new ArrayList<>(List.of(Bot.class.getDeclaredMethods()));
    methods.sort(Comparator.comparing(Method::getName));
 
    for (Method method : methods) {
        Command command = method.getAnnotation(Command.class);
        if (command != null) {
            IO.println(command.name() + " — " + command.description());
        }
    }
}

Output:

/help — no description
/start — greeting

The internal method isn't annotated, and getAnnotation returned null for it. The order of methods from getDeclaredMethods isn't guaranteed, so the list is sorted by name. This is exactly how frameworks find command handlers, URL routes and tests.

Common mistake

If you forget @Retention(RetentionPolicy.RUNTIME), by default the annotation doesn't make it into the running program, and reflection won't see it:

@interface Command {
    String name();
}
 
class Bot {
    @Command(name = "/start")
    void start() {}
}
 
void main() throws NoSuchMethodException {
    Method method = Bot.class.getDeclaredMethod("start");
    IO.println(method.isAnnotationPresent(Command.class));
}

Output:

false

There's no error, the annotation just "disappeared". For annotations read at run time, always specify RUNTIME.

Thanks for reading!

Donate to the site — we work hard for you.