JavaAnnotations
This tutorial describes how to use null annotations to avoid null pointer exceptions and how to define your own annotations and how to use Java reflection to analyze your code with it.
1. Defining the API contract via null annotations
1.1. Eclipse IDE and null annotations
Annotations can be used to provide additional meta information about your API. Eclipse JDT provides the following annotations which can be used:
-
@NonNull
: null is not a legal value -
@Nullable
: null value is allowed and must be expected
These annotations can be placed on:
-
Method parameter
-
Method return (syntactically a method annotation is used here)
-
Local variables
-
Fields
You can also specify the default value via the @NonNullByDefault
annotation which can be specified for a method, a type or a package (via a file package-info.java).
To specify it for a whole package, create the following package-info.java
file in your package, for example, com.vogella.test.nullannatations
.
@NonNullByDefault
package com.vogella.test.nullannatations;
import org.eclipse.jdt.annotation.NonNullByDefault;
1.2. Adding support for null annotations
1.2.1. Maven
<dependency>
<groupId>org.eclipse.jdt</groupId>
<artifactId>org.eclipse.jdt.annotation</artifactId>
<version>2.0.0</version>
</dependency>
1.2.2. Gradle
compile 'org.eclipse.jdt:org.eclipse.jdt.annotation:2.0.0'
1.2.3. OSGi
Add an optional dependency to org.eclipse.jdt.annotation
in your MANIFEST.MF
1.2.4. Managing the classpath manually
Eclipse provides a quickfix for adding the required dependency. Simple use @NonNull
in your code and use Ctrl+1 to add the required dependency.
1.3. Activating annotation based null pointer access analysis
Select
and select Enable annotation-based null analysis.2. Defining custom annotations
2.1. Define your custom annotation
The Java programming language allows you to define your custom annotations.
Annotations are defined via the @interface
annotation before the class name.
Via @Retention
you define if the annotation should be retained at runtime or not.
The @Target
annotation lets you define where this annotation can be used, e.g., the class, fields, methods, etc.
A typical annotation definition would look like the following.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface InstallerMethod {
}
You can add additional information, for example you can define that your annotation is a qualifier for the `@Inject`annotation.
@javax.inject.Qualifier
@Documented
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface Checker {
}
2.2. Accessing your annotation via Java reflection
To process your annotation you could write your own annotation processor. Typically you use Java reflection for this. Java reflection allows you to analyze a Java class and use the information contained in this class at runtime.
3. Exercise: Define custom annotation and access via reflection
Create a new Java project called com.vogella.annotations
.
Create the following two classes. The first class defines an annotation and the second class uses this to mark certain methods.
package com.vogella.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(value = ElementType.METHOD)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface CanRun {
}
package com.vogella.annotations;
import java.lang.reflect.Method;
public class AnnotationRunner {
public void method1() {
System.out.println("method1");
}
@CanRun
public void method2() {
System.out.println("method2");
}
public void method3() {
System.out.println("method3");
}
@CanRun
public void method5() {
System.out.println("method4");
}
}
Afterwards create the following test class. The main method of this class analyzes the annotations and calls the corresponding methods.
package com.vogella.annotations;
import java.lang.reflect.Method;
public class MyTest {
public static void main(String[] args) {
AnnotationRunner runner = new AnnotationRunner();
Method[] methods = runner.getClass().getMethods();
for (Method method : methods) {
CanRun annos = method.getAnnotation(CanRun.class);
if (annos != null) {
try {
method.invoke(runner);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}