Introducing Annotations
Annotations are all about metadata.
Purpose of Annotations
The purpose of an annotation is to assign metadata attributes to classes, methods, variables and other Java types.
They function a lot like interfaces. They allow us to mark a class, without chaning its inheritance structure. While interfaces can be applies only to classes, annotations can be applied to classes, methods, expressions and other annotations. Unlike interfaces, annotations allow us to pass a set of values where they’re applied.
public class Veterinarian {
@ZooAnimal(habitat="Infirmary")
private Lion sickLion;
@ZooAnimal(habitat="Safari")
private Lion healthyLion;
@ZooAnimal(habitat="Special Enclosure")
private Lion blindLion;
}
This class defines three variables, each with an associated habitat value. The habitat is part of the type declaration of each variable, not an individual object.
public class Lion {
@ZooSchedule(hours={"9am", "5pm", "10pm"})
void feedLions() {
sout("time to feed the lions!");
}
}
public class Peacock {
@ZooSchedule(hours={"4pm", "5pm"})
void cleanPeacocksPen() {
sout("time to sweep up!");
}
}
With this approach, the task and its schedule are defined right next to each other.
Rules of annotations
- Annotations function a lot like interfaces. (They allow us to mark a class without changing its inheritance structure)
- Annotations establish relationships that make it easier to manage data about our application.
- An annotation ascribes custom information on the declaration where it’s defined. (The same annotation can often be applied to completely unrelated classes or variables)
- Annotations are optional metadata and by themselves do not do anything. (You have to be able to take a project filled with thousands of annotations, remove all of them, and it will still compile and run. Just with potentially different behaviour at runtime)
- To use an annotation, all required values must be provided
- To declare a
@Repeteable
annotation, you must define a containing annotation type value
Creating Custom Annotations
Creating an Annotation
We use the @interface
annotation to declare one. They’re commonly defined in their own file as a top-level type, although they can be defined inside a class declaration like an inner class.
public @interface Exercise {}
When declaring an annotation, any element without a default value, is considered required.
We use this interface like this
@Exercise() public class Cheetah {}
@Exercise public class Sloth {}
@Exercise public class ZooEmployee {}
Like interfaces, annotations can be applied to unrelated classes.
When using an annotation, parentheses are optional. Once we start adding elements, though, they’re required if the annotation includes any values.
If an annotation is declared on a line by itself, then it applies to the next nonannotation type found on the proceeding lines. This applies when there’re multiple annotations.
@Scaley @Flexible
@Food("insect") public class Snake {}
Specifying a Required Element
An annotation element is an attribute that stores values about the particular usage of an annotation.
public @interface Exercise {
int hoursPerDay();
}
Let’s check the usage
@Exercise(hoursPerDay=3) public class Cheetah {}
@Exercise hoursPerDay=3 public class Sloth {} // DOES NOT COMPILE
@Exercise public class ZooEmployee {} // DOES NOT COMPILE
The Sloth
class does not compile because it’s missing parentheses.
The last one does not compile neither because hoursPerDay
is required.
Providing an Optional Element
For an element to be optional, rather than required, it must include a default value.
public @interface Exercise {
int hoursPerDay();
int startHour() default 6;
}
Let’s apply it.
@Exercise(startHour=5, hoursPerDay=3) public class Cheetah {}
@Exercise(hoursPerDay=0) public class Sloth {}
@Exercise(hoursPerDay=7, startHour="8") // DOES NOT COMPILE
public class ZooEmployee {}
The order for each element does not matter.
The last one does not compile because the value type is invalid.
The default value of an annotation cannot be just any value. It has to be a non-null constant expression.
public @interface BadAnnotation {
String name() default new String(""); // DOES NOT COMPILE
String address() default "";
String title() default null; // DOES NOT COMPILE
}
Selecting an Element type
An annotation element cannot be declared with just any type.
It has to be a
- primitive
- String
- Class
- enum
- another annotation
- an array of these types
public class Bear {}
public enum Size { SMALL, MEDIUM, LARGE }
public @interface Panda {
Integer height(); // DOES NOT COMPILE
String[][] generalInfo(); // DOES NOT COMPILE
Size size() default Size.SMALL;
Bear friendlyBear(); // DOES NOT COMPILE
Exercise exervise() default @Exercise(hoursPerDay=2);
}
Wrapper classes like Integer
or Long
are not supported.
String[]
is supported but String[][]
is not.
Bear
is not supported as is not Class
.
Applying Element Modifiers
Annotation elements are implicitly abstract
and public
.
public @interface Material {}
public @interface Fluffy {
int cuteness();
public abstract int softness() default 11;
protected Material material(); // DOES NOT COMPILE
private String friendly(); // DOES NOT COMPILE
final boolean isBunny(); // DOES NOT COMPILE
}
The elements material()
and friendly()
do not compile because the access modifier conflicts with the elements being implicitly public
.
The method isBunny()
does not compile because it cannot be marked final
.
Adding a Constant Variable
Annotations can include constant variables that cna be accessed by other classes without actually creating the annotation.
public @interface ElectricitySource {
public int voltage();
int MIN_VOLTAGE = 2;
public static final int MAX_VOLTAGE = 18;
}
They’re also implicitly public
, static
and final
.
Reviewing Annotation Rules
public @interface Hazard {
int danger();
public String description() default "Fire";
public static final int UNKNOWN = -1;
}
@Hazard(danger=100, description="Wind Damage")
public class Tornado {}
public @interface Hazard
- public
is optional, as it may be package-private.
public static final int UNKNOWN
- public static final
is implicitly applied, so they’re also optional.
Applying Annotations
Using Annotations in Declarations
They can be applied to a miriad of things. The following compiles, assuming they exist.
@FunctionalInterface interface Speedster {
void go(String name);
}
@LongEars
@Soft
@Cuddly
public class Rabbit {
@Deprecated
public Rabbit(@NotNull Integer size) {}
@Speed(velocity="fast")
public void eat(@Edible String input) {
@Food(vegetarian=true)
String m = (@Tasty String) "carrots";
Speedster s1 = new @Racer Speedster() {
public void go(@FirstName @NotEmpty String name) {
sout("start!" + name);
}
}
Speedster s2 = (@Valid String n) -> sout(n);
}
}
Mixing Required and Optional Elements
To use an annotation, all required values must be provided. While it may have many elements, valure are required only for ones without default values.
public @interface Swimmer {
int armLength = 10;
String stroke();
String name();
String favoriteStroke() default "Backstroke";
}
Implementations
@Swimmer class Amphibian {} // DOES NOT COMPILE
@Swimmer(favoriteStroke="Breaststroke", name="Sally") class Tadpole {} // DOES NOT COMPILE
@Swimmer(stroke="FrogKick", name="Kermit") clas Frog {}
@Swimmer(stroke="Butterfly", name="Kip", armLength=1) class Reptile {} // DOES NOT COMPILE
@Swimmer(strike="", name="", favoriteStroke="") class Snake {}
The 1st line does not compile because it’s missing the required elements stroke()
and name()
.
The 2nd one does not compile neither as it’s missing the required element stroke()
.
The 4th lines does not compile as armLenght
is a constant, not an element, and cannot be included in an annotation.
Creating a value() element
@Injured("Broken Tail") public class Monkey {}
Notice the previous annotation does only include a value and not a property name. This may compile as long as:
- The annotation declaration has an element named
value()
, which may be optional or required. - The annotation declaration must not contain any other elements that are required.
- The annotation usage must not provide values for any other elements.
public @interface Injured {
String veterinarian() default "unassigned";
String value() default "foot";
int age() default 1;
}
Valid usages
public abstract class Elephant {
@Injured("Legs") public void fallDown() {}
@Injured(value="Legs") public abstract int trip();
@Injured String injuries[];
}
The last one is allowed as @Injured
does not have any required elements.
Typically, the value()
of an annotation should be related ot its name.
(!) For the exam, make sure that if the shorthand notation is used, then there’s an element named value()
. Also check that there’re no other required elements.
For example, the following declarations cannot be used with a shorthand annotation.
public @interface Sleep {
int value();
String hours();
}
public @interface Wake {
String hours();
}
@Injured("Fur", age=2) public class Bear{} // DOES NOT COMPILE
The last annotation is not valid as it provides more than one value.
Passing an Array of Values
Annotations support a shorthand notation for providing an array that contains a single element.
public @interface Music {
String[] genres();
}
If we want to provide only one value to the array, either way is correct
public class Giraffe {
@Music(genres={"Rock and roll"}) String mostDisliked;
@Music(genres="Classical") String favorite;
}
The first annotation is considered the regular form, as it’s clear the usage for an array.
The second one is the shorthand notation, where the array brackets are dropped for convenience. This notation can be used only if the array is composed of a single element.
public class Reindeer {
@Music(genres="Blues", "Jazz") String favorite; // DOES NOT COMPILE
@Music(genres=) mostDisliked; // DOES NOT COMPILE
@Music(genres=null) String other; // DOES NOT COMPILE
@Music(genres={}) String alternate;
}
While this shorthand notation can be used for arrays, it does not work for List
or Collection
.
Declaring Annotation-Specific Annotations
Limiting Usage with @Target
Many annotations declarations include @Target
annotation, which limits the types the annotation can be applied to.
This takes an array of ElementType
as its value()
element.
ElementType Values
|ElementType value|Applies to|
|:—:|:—:|
|TYPE|Classes, interfaces, enums, annotations|
|FIELD|Instance and static variables, enum values|
|METHOD|method declarations|
|PARAMETER|Constructor, method, lambda parameters|
|CONSTRUCTOR|Constructor declarations|
|LOCAL_VARIABLE|Local variables|
|ANNOTATION_TYPE|Annotations|
|PACKAGE|Packages declared in package-info.java
|
|TYPE_PARAMETER|Parameterized types, generic declarations|
|TYPE_USE|Able to be applied anywhere there’s a Java type declared or used|
|MODULE|Modules|
Some of this elements overlap.
(!) Make sure you’re able to properly recognize usage of them for the exam. (!)
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
public @interface ZooAttraction {}
Understanding the TYPE_USE value
The TYPE_USE
parameter can be used anywhere there’s a Java type.
By including it in @Target
it actually includes nearly all the values, with a few exceptions, such as methods that return void
.
It also allows annotations in places where types are used, such as cast operations, object creation with new
, inside type declarations etc.
(!) For the exam, you only need to recognize that it can be applied in this manner if TYPE_USE
is one of the @Target
options (!)
@Target(ElementType.TYPE_USE)
@interface Technical {}
public class NetworkRepair {
class OutSrc extends @Technical NetworkRepair {}
public void repair() {
var repairSubclass = new @Technical NetworkRepair() {};
var o = new @Technical NetworkRepair().new @Technical OutSrc();
int remaining = (@Technical int) 10.0;
}
}
Storing Annotations with @Retention
The compiler discards certain types of information when converting your source code into a .class
file. This may include annotations, which may be discarded by the compiler or at runtime.
This is specified by the @Retention
annotation. It uses the following values of RetentionPolicy
.
RetentionPolicy value | Description |
---|---|
SOURCE | Used only in the source file, discarded by the compiler |
CLASS | Stored in the .class file but not available at runtime (default compiler behaviour) |
RUNTIME | Stored in the .class file and available at runtime |
@Retention(RetentionPolicy.CLASS) @interface Flier {}
@Retention(RetentionPolicy.RUNTIME) @interface Swimmer {}
Generating Javadoc with @Documented
If present, then the generated Javadoc will include annotation information defined on Java types.
Because it’s a marker annotation, it doesn’t take any values.
@Documented public @interface Hunter {}
@Hunter public class Lion {}
Here, the @Hunter
annotation would be published with the Lion
Javadoc information.
Inheriting Annotations with @Inherited
When this annotation is applied to a class, subclasses will inherit the annotation information found in the parent class.
@Inherited public @interface Vertebrate {}
@Vertebrate public class Mammal {}
public class Dolphin extends Mammal {} // includes @Vertebrate
Supporting Duplicates with @Repeatable
It’s used when you want to specify an annotation more than once on a type. Generally this happens when you want to apply the same annotation with different values.
public class Zoo {
public static class Monkey {}
@Risk(danger="Silly")
@Risk(danger="Aggressive", level=5)
@Risk(danger="Violent", level=10)
private Monkey monkey;
}
(!) In the exam, if an annotation is being repeated for the same type, be sure it correctly implements @Repeteable
(!)
To declare a @Repeteable
annotation, you must define a containing annotation type value. A containing annotation type is a separate annotation that defines a value()
array element. The type of this array is the particular annotation you want to repeat. By convention, the name is often the plural form of the repeteable annotation.
public @interface Risks {
Risk[] value();
}
@Repeteable(Risks.class)
public @interface Risk {
String danger();
int level() default 1;
}
- The repeteable annotation must be declared with
@Repeteable
and contain a value that refers to the containing type annotation. - The containing type annotation must include an element named
value()
, which is a primitive array of the repeteable annotation type.
Using Common Annotations
This set of annotations apply to various types and methods, and have special rules. If they’re used incorrectly, the compiler will report an error.
Marking Methods with @Override
It’s a marker annotation that’s used to indicate a method is overriding an inherited method.
The overriding method must have
- the same signature
- same or broader access modifier
- a covariant return tyep
- not declare any new or broader checked exceptions
public interface Intelligencce {
int cunning();
}
public class Canine implements Intelligence {
@Override
public int cunning() {
return 500;
}
}
(!) During the exam, you should be able to identify anywhere this annotation is used incorrectly (!)
public class Dog extends Canine {
@Override // DOES NOT COMPILE
public boolean playFetch() {
return true;
}
@Override // DOES NOT COMPILE
void cunning(int timeOfDay) {
}
}
Declaring interfaces with @FunctionalInterface
This marker annotation can be applied to any valid functional interface.
@FunctionalInterface
public interface Intelligence {
int cunning();
}
The compiler will throw an error when applied to anything other than a valid functional interface.
@FunctionalInterface
abstract class Reptile { // DOES NOT COMPILE
abstract String getName();
}
(!) check and be sure the it’s applied only to interfaces (!)
Retiring Code with @Deprecated
This notifiers users that a new version of the method is available and gives them time to migrate their code to the new version, before we finally remove the old version.
It supports two optional values since()
and boolean forRemoval()
.
@Deprecated
public class ZooPlanner {
}
@Deprecated(since="1.8", forRemoval=true)
public class ZooPlanner {
}
Ignoring Warnings with @SuppressWarnings
While the compiler can be helpful in warning you of potential coding problems, sometimes you need to perform a particular operation. Applyin this annotation disables this warnings.
It takes two values.
Value | Description |
---|---|
“deprecation” | Ignore warnings related to types or methods marked with @Deprecated annotation |
“unchecked” | Ignore warnings related to the use of raw types, such as List |
@SupressWarnings("deprecation")
public void wakeUp() {
SongBird.sing(10);
}
@SupressWarnings("unchecked")
public void goToBed() {
SongBird.chirp(new ArrayList());
}
Protecting Arguments with @SafeVarargs
It indicates that a method does not perform any potential unsafe operations on its varargs parameters. It can be applied only to constructors or methods that cannot be overriden.
The annotation it’s used to indicate to other developers that your method does not perform any unsafe operations.
public class NeverDoThis {
final int thisIsUnsafe(List<Integer>... carrot) {
Object[] stick = carrot;
stick[0] = Arrays.asList("nope!");
return carrot[0].get(0); // ClassCastException at runtime
}
public static void main(String[] a) {
var carrot = new ArrayList<Integer>();
new NeverDoThis().thisIsUnsafe(carrot);
}
}
This code compiles, although it generates two compiler warnings.
[Line 4] Type safety: Potential heap pollution via varargs parameter carrot
[Line 11] Type safety: A generic array of List<Integer> is created for a varargs parameter
We can remove both compiler warnings by adding the @SafeVarargs
annotation.
@SafeVarargs
final int thisIsUnsafe(List<Integer>... carrot) {
We didn’t fix the unsafe operation. It still throws ClassCastException
. However, we made it so the compiler won’t warn us about it anymore.
(!) For the exam, you just need to be able to identify unsafe operations and know they often involve generics. You should also know the annotation cna be applied only to methods that contain a varargs parameter and are not able to be overriden. (!)
@SafeVarargs
public static void eat(int meal) {} // DOES NOT COMPILE (missing varargs)
@SafeVarargs
protected void drink(String... cup) {} // DOES NOT COMPILE (not static, final or private)
@SafeVarargs
void chew(boolean... food) {} // DOES NOT COMPILE (not static, final or private)
Summary
In this chapter, we taught you everything you need to know about annotations for the exam. Ideally, we also taught you how to create and use custom annotations in your daily programming life. As we mentioned early on, annotations are one of the most convenient tools available in the Java language.
For the exam, you need to know the structure of an annotation declaration, including how to declare required elements, optional elements, and constant variables. You also need to know how to apply an annotation properly and ensure required elements have values. You should also be familiar with the two shorthand notations we discussed in this chapter. The first allows you to drop the elementName under certain conditions. The second allows you to specify a single value for an array element without the array braces ({}).
You need to know about the various built-in annotations available in the Java language. We sorted these into two groups: annotations that apply to other annotations and common annotations. The annotationspecific annotations provide rules for how annotations are handled by the compiler, such as specifying an inheritance or retention policy. They can also be used to disallow certain usage, such as using a methodtargeted annotation applied to a class declaration.
The second set of annotations are common ones that you should know for the exam. Many, like @Override and @FunctionalInterface, are quite useful and provide other developers with additional information about your application.
Exam Essentials
Be able to declare annotations with required elements, optional elements, and variables. An annotation is declared with the @interface type. It may include elements and public static final constant variables. If it does not include any elements, then it is a marker annotation. Optional elements are specified with a default keyword and value, while required elements are those specified without one.
Be able to identify where annotations can be applied. An annotation is applied using the at (@) symbol, followed by the annotation name. Annotations must include a value for each required element and can be applied to types, methods, constructors, and variables. They can also be used in cast operations, lambda expressions, or inside type declarations.
Understand how to apply an annotation without an element name. If an annotation contains an element named value() and does not contain any other elements that are required, then it can be used without the elementName. For it to be used properly, no other values may be passed.
Understand how to apply an annotation with a single-element array. If one of the annotation elements is a primitive array and the array is passed a single value, then the annotation value may be written without the array braces ({}).
Apply built-in annotations to other annotations. Java includes a number of annotations that apply to annotation declarations. The @Target annotation allows you to specify where an annotation can and cannot be used. The @Retention annotation allows you to specify at what level the annotation metadata is kept or discarded. @Documented is a marker annotation that allows you to specify whether annotation information is included in the generated documentation. @Inherited is another marker annotation that determines whether annotations are inherited from super types. The @Repeatable annotation allows you to list an annotation more than once on a single declaration. It requires a second containing type annotation to be declared.
Apply common annotations to various Java types. Java includes many built-in annotations that apply to classes, methods, variables, and expressions. The @Override annotation is used to indicate that a method is overriding an inherited method. The @FunctionalInterface annotation confirms that an interface contains exactly one abstract method. Marking a type @Deprecated means that the compiler will generate a depreciation warning when it is referenced. Adding @SuppressWarnings with a set of values to a declaration causes the compiler to ignore the set of specified warnings. Adding @SafeVarargs on a constructor or private, static, or final method instructs other developers that no unsafe operations will be performed on its varargs parameter. While all of these annotations are optional, they are quite useful and improve the quality of code when used.