Annotations¶
Introduction¶
Annotations are a form of metadata that provides information about a program that is not part of it. Annotations do not directly affect the operation of the code they refer to.
Annotations implement 3 basic functionalities:
- providing information for the compiler - the compiler can use the annotation to detect errors or hide warnings
- information processing to generate code, XML files, etc. during compilation
- the annotations are available for checking at runtime
Annotation basics¶
An annotation tag starts with the '@' sign which tells the compiler that it is dealing with an annotation and a name, e.g.
@Override
Annotations can also take arguments, e.g.
@SuppressWarnings(value = "unchecked") // annotation with a single argument
@LogPotencialErrors(showStackTrace = "true", format = "true") // annotation with two arguments
Note that if the only argument passed to the annotation is an argument named value
, this key may be omitted. In case of using more than one argument, we must always give all their names. This behavior is explained in the following example:
@SuppressWarnings(value = "unchecked") // use of value argument
@SuppressWarnings("unchecked") // an entry equivalent to that in the line above
@SomeOtherAnnotation(value = "someValue", otherValue = "someOtherValue") // the case where we can NOT omit the key 'value'
NOTE: Multiple annotations can be used on one item.
Usage¶
We can use annotations in many places in our code. The elements where this is possible include:
- class and interface definitions
- class fields
- constructors, methods and their arguments
The elements on which we can apply a given annotation depends on how it was defined.
NOTE: The order of annotations used on a given element does not matter.
Defining annotations¶
Annotations are defined similar to interfaces, the only difference is adding an @
sign before the keyword.
public @interface ComponentInfo {
String name();
String date();
int currentRevision();
}
In the example above, name ()
, date ()
, and currentRevision ()
are not methods, but elements of the annotation. Their name most often differs from the names of common methods. There is no point in looking for verbs in them (such as get
,set
or do
). Elements defined this way become arguments of such annotation. An example use of the annotation defined above could look like this:
@ComponentInfo(name = "someName", date = "10-12-2021", currentRevision = 3)
NOTE: The order in which the values of the arguments in the annotation are defined does not matter.
Limitations¶
While defining an annotation and its elements ("methods"), we must bear in mind some limitations:
- elements can only return certain types, including:
- simple types (e.g.
int
,float
,double
) and their object-oriented counterparts (e.g.Integer
,Double
) - String class
- enums
- other annotations
- arrays of the types as mentioned above
- simple types (e.g.
- these elements cannot take any arguments (i.e. look like method declarations with no arguments)
Below is another valid example of an annotation declaration, using arrays and the Month
enum in its definition:
public @interface PersonInfo {
String[] names();
String[] dates();
Month month(); // java.time.Month is an enumerated type
}
// sample use
@PersonInfo(names = {"Alice", "Andrew"}, dates = {}, month = Month.APRIL)
default¶
The annotation definitions in the previous examples required us to specify values for all items when using them. The default keyword allows you to define the default values of annotation elements, which are used at the end of the defined annotation element, e.g.
public @interface InputArgs {
int intValue() default 2; // the default value of the intValue field if omitted is 2
double doubleValue(); // no default value, required field
}
Use in examples¶
The annotations defined in the previous examples can be used in the [previously] (#use) places of the application, e.g.
@PersonInfo(names = {}, dates = "12-12-2022", month = Month.AUGUST)
public class AnnotationsExamples {
@ComponentInfo(name = "annotationsExamples", date = "12-10-2020", currentRevision = 2)
private String someField;
public AnnotationsExamples(@InputArgs(doubleValue = 7) final String someField) { //intValue is set to the default
this.someField = someField;
}
}
Annotations built into the language¶
Various kinds of annotations are defined in Java SE. Some annotation types are used by the Java compiler and some change the behavior of other annotations. Some of the basic and most used are:
@Deprecated
- it allows you to mark a method/class as obsolete and one that should no longer be used.@Override
- tells the compiler that the element is to replace the element declared in the parent class.@SuppressWarnings
- tells the compiler to suppress certain warnings it would otherwise generate.
There are also special annotations that can be used to specify other annotations, these are called meta-annotations, e.g.:
@Documented
- auxiliary annotation that can e.g. display documentation with javadoc.@Repeatable
- the annotation, introduced in Java SE 8, indicates that a particular annotation can be applied more than once on the same element (e.g. a field).@Inherited
- the annotation indicates that the annotations used are visible during class inheritance.@FunctionalInterface
- annotation pointing to interface that has one abstract method.
We could use the annotations defined so far on [any] (#use) elements. When defining an annotation intended for use by other programmers, we want to indicate where and at what point such an annotation will be processed. There are two annotations for this:
@Retention
- which specifies the time during which the annotation is visible.@Target
- which specifies a list of items on which the annotation can be applied.
Using the @Retention
annotation we can indicate the following value of thevalue
field:
RetentionPolicy.SOURCE
- specifies that the annotation is only visible at the source code level and is ignored by the compiler.RetentionPolicy.CLASS
- specifies that the annotation is visible to the compiler at compile time but is ignored by the JVM.RetentionPolicy.RUNTIME
- specifies that the annotation is stored by the JVM so that it can be used at runtime (ie while the program is running).
In turn, to the Target
annotation we have to pass a value of the ElementType
type, which has many possible values, including:
wartość | elements on which annotation can be applied |
---|---|
ElementType.CONSTRUCTOR | onstructors declarations |
ElementType.FIELD | class fields |
ElementType.METHOD | method declarations |
ElementType.TYPE | class, interface, enum definitions |
ElementType.PARAMETER | method parameter |
In the example below, we declare an annotation that is visible at runtime, which we can define on the class declaration and class fields:
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.FIELD })
public @interface NameFilter {
}
@NameFilter // correct use -> ElementType.TYPE
public class NameFilterUsage {
@NameFilter // correct use -> ElementType.FIELD
private String field;
@NameFilter // INCORRECT use -> no ElementType.METHOD
public void setField(@NameFilter final String field) { // INCORRECT use -> no ElementType.PARAMETER
this.field = field;
}
}