JavaSE - Reflection - Annotation

JavaSE - Reflection - Annotation

Learning objectives of this section:

  • Understand the concept of annotation;
  • Understand and master the use of annotations;
  • Understand and master the Annotation interface and JDK meta Annotation;
  • Understand and master the writing method of custom annotation;
  • Understand and master how to use reflection to process annotations;
  • Understand some annotations provided by JDK.

1. General notes

1.1 introduction to notes

Starting from JDK5, Java has added support for metadata, that is, annotations. Annotations are different from annotations. Annotations can be understood as special tags in the code. These tags can be read and processed during compilation, class loading and runtime. Through annotation, developers can embed supplementary information in the source code without changing the original code and logic.

COMMENTS - Baidu Encyclopedia

Java Annotation, also known as Java Annotation, is an Annotation mechanism introduced by JDK5.0. Classes, methods, variables, parameters and packages in the Java language can be labeled. Unlike Javadoc, Java annotations can obtain Annotation content through reflection. When the compiler generates class files, annotations can be embedded in bytecode. The Java virtual machine can retain the Annotation content and obtain the Annotation content at run time. Of course, it also supports custom Java annotations.

Java Annotation - rookie tutorial

Annotation, also known as annotation, is a new annotation mechanism introduced by JDK1.5.

Annotation is actually a special tag with the prefix @ in the code (such as @ Override). Annotation is an interface. Annotations can be read and processed during compilation, class loading, and runtime. By using annotations, users can, without changing the original logic,
Some supplementary information is embedded in the source code. Code analysis tools, development tools or deployment tools can be verified or deployed through this information.

1.2 role of notes

Annotations can be used like modifiers to modify packages, classes, methods, variables, parameters, and even return values. This information is stored in key value pairs inside the annotation.

In Java se development, annotations are used for simple purposes, such as marking outdated functions, ignoring some warnings, etc. Annotations play a more important role in Java EE development or Android development. For example, they are used to configure some functions of applications to replace the traditional XML based configuration and redundant code of Java EE.

Future Java development patterns are based on annotations, such as JPA specification, Spring framework, Hibernate framework, struts 2 framework, etc. Annotation is a trend. To some extent, it can be said:

Frame = annotation + reflection + design pattern

2. Usage of notes

Use the three basic annotations @ Override, @ Deprecated and @ SuppressWarnings provided by JDK to demonstrate the usage of annotations:

2.1 @Override

When the @ Override annotation is marked on a method, it means that the method overrides the method in the parent class or interface:

class Person {
    public void eat() {
        System.out.println("People are eating");
    }
}
interface Studdable {
    void study();
}
public class Student extends Person implements Studdable {
    @Override // Overriding the method of the parent class Person
    public void eat() {
        System.out.println("The students are eating");
    }
    @Override // The method of interface Studdable is implemented
    public void study() {
        System.out.println("The students are studying");
    }
    @Override // Overriding the method of the parent class Object
    public String toString() {
        return "I am a student";
    }
}

@The Override annotation does not change any function. Its purpose is to tell the compiler to check whether the marked method overrides the method in the parent class or interface. If a non overridden method is forcibly annotated with the @ Override annotation, the compiler will report an error:

public class Test {
    @Override
    public void test() {
    }
}

When trying to compile, the compiler reported an error:

2.2 @Deprecated

The annotated @ Deprecated structures (classes, interfaces, methods, variables, etc.) are outdated structures, indicating that they are no longer recommended:

class Human {
    @Deprecated
    public void crawl() {
        System.out.println("People crawl and walk");
    }
    public void run() {
        System.out.println("People stand and walk");
    }
}
public class Test {
    public static void main(String[] args) {
        Human human = new Human();
        human.crawl();
        human.run();
    }
}

Attempt to compile, compile successfully, run result:

In practice, it can be found that the method annotated with @ Deprecated annotation can still be used, but a warning will be issued during compilation, indicating that the outdated method is used@ The Deprecated annotation does not change any function, but only prompts the user that this method is not recommended for some reasons (such as unsafe or there is a better alternative). However, in order to ensure compatibility, the code using this method will not be affected, so you choose to annotate the @ Deprecated annotation without removing it.

2.3 @SuppressWarnings

The @ SuppressWarnings annotation is used to suppress some warnings of the compiler. It needs to provide a parameter:

parametertypeexplain
valueString[]You can select multiple types of warnings to suppress

Some warning types that can be suppressed (for a complete list, see: Excluding warnings using @SuppressWarnings - eclipse ):

Warning typeexplain
allAll warnings
deprecationStructure obsolescence warning
rawtypesWarning about using primitive types with generics
serialSerialization class did not provide serialVersionUID warning
uncheckedCall primitive type structure warning
unusedStructure not used warning

The @ SuppressWarnings annotation can be used to suppress compile time warnings generated by the @ Deprecated annotation in Chapter 2.2:

class Human {
    @Deprecated
    public void crawl() {
        System.out.println("People crawl and walk");
    }
    public void run() {
        System.out.println("People stand and walk");
    }
}
public class Test {
    @SuppressWarnings("deprecation")
    public static void main(String[] args) {
        Human human = new Human();
        human.crawl();
        human.run();
    }
}

Attempt to compile, compile successfully, run result:

When the @ SuppressWarnings annotation is used, the compiler does not output warnings.

3. Annotation interface

The Annotation interface is located under the java.lang.annotation package. It is the parent interface of all annotations and defines the basic methods in the Annotation interface:

methodreturn typefunction
equals(Object obj)booleanInherit from the Object class and compare whether it is the same as the Object obj
hashCode()intInherits from the Object class and returns the hash value of the current Object
toString()StringInherits from the Object class and returns the string form of the current Object
annotationType()Class<? extends Annotation>Returns the type of the current annotation

All annotations inherit the Annotation interface directly or indirectly.

4. Meta annotation

Java provides five meta annotations for the annotation interface. Meta annotation s are called annotations in annotations. They define the characteristics and usage of annotations.

4.1 @Retention

Meta annotation @ Retention is used to restrict the Retention rules of annotations and specify how long annotations can be retained. It requires the following parameters:

parametertypeexplain
valueRetentionPolicyRetention rules for annotations

RetentionPolicy is an enumeration class located in the java.lang.annotation package. The constants defined by RetentionPolicy specify the retention rules of annotations:

  • RetentionPolicy.SOURCE:
    Annotation information is only kept in the source code and will be removed by the compiler at compile time;
  • RetentionPolicy.CLASS:
    The annotation information will be retained in the bytecode file, but the Java virtual machine cannot access it during operation. If an annotation does not use @ Retention annotation, this Retention rule is used by default;
  • RetentionPolicy.RUNTIME:
    The annotation information will be kept in the bytecode file, which can be accessed by the Java virtual machine during operation, and the annotation information can be obtained by reflection.

4.2 @Target

Meta annotation @ Target is used to constrain the annotation position. It specifies where the annotation can only be marked. It needs to provide parameters:

parametertypeexplain
valueElementType[]Annotation location

ElementType is an enumeration class located in the java.lang.annotation package. Its defined constants specify the annotation location:

constantexplain
ElementType.TYPEAnnotations can be marked on classes, interfaces (annotations) and enumeration classes
ElementType.FIELDAnnotations can be annotated on member variables, including constants of enumeration classes
ElementType.METHODAnnotations can be marked on methods
ElementType.PARAMETERAnnotations can be marked on the parameters of the method
ElementType.CONSTRUCTORAnnotations can be marked on the construction method
ElementType.LOCAL_VARIABLEAnnotations can be annotated on local variables
ElementType.ANNOTATION_TYPEAnnotations can be annotated on other annotations (make the annotated annotations become meta annotations. In fact, the annotation positions of meta annotations are all this value)
ElementType.PACKAGEComments can be marked on the package (used in package-info.java to output comments on the package when running javadoc command)
ElementType.TYPE_PARAMETERJDK1.8 provides that annotations can be marked on the declaration of generics
ElementType.TYPE_USEJDK1.8 provides that annotations can be marked in any use type

Write code to test:

import static java.lang.annotation.ElementType.*;
import java.lang.annotation.Target;

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE, TYPE_PARAMETER, TYPE_USE})
@interface Type {
    String value();
}

@Type("ANNOTATION_TYPE,annotation")
@interface Anno {
}

@Type("TYPE,class")
public class Test {
    @Type("FIELD,Member variable")
    private String field;
    @Type("FIELD,Static variable")
    public static String staticField = "static";
    @Type("FIELD,static const ")
    public static final String constField = "const";
    @Type("CONSTRUCTOR,Construction method")
    public Test(@Type("PARAMETER,parameter") String field) {
        this.field = field;
    }
    @Type("METHOD,Member method")
    public void foo() {
        @Type("LOCAL_VARIABLE,local variable")
        String local = "local";
    }
    @Type("METHOD,Static method")
    public static void staticFoo() throws @Type("TYPE_USE,Use type") Exception {
        Object o = 15;
        Integer i = (@Type("TYPE_USE,Use type") Integer) o;
    }
    public <@Type("TYPE_PARAMETER,Generic declaration") T> void parameterTypeFoo() {
    }
}

4.3 @Documented

Annotations annotated by meta annotation @ Documented will be extracted into documents when using javadoc tools.

For example, the annotation @ Deprecated is internally annotated by the meta annotation @ Documented:

// java.lang.Deprecated
// Deprecated.java jdk1.8.0_202 Line:41~45

@Documented // Annotated by @ Documented meta annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

Using the annotation @ Deprecated annotation structure, the @ Documented annotation will also be recorded in the document after the document is generated by javadoc tool.

Write code to test:

public class Test {
    @Deprecated
    public void foo() {
        System.out.println("test method");
    }
}

Run the command javadoc Test.java to generate a javadoc document. In the document, you can see that this method is annotated with @ Deprecated annotation and indicates that it is outdated:

Note: for the annotation marked by meta annotation @ Documented, its retention rule must be RetentionPolicy.RUNTIME.

4.4 @Inherited

Annotations annotated with @ Inherited meta annotation are Inherited. If a class is annotated with @ Inherited meta annotation, its subclasses will be automatically annotated with the annotation it annotates.

Write code to test:

import java.lang.annotation.*;
import java.util.Arrays;

@Inherited // The custom annotation @ MyAnno is annotated by the meta annotation @ Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface MyAnno { // Custom annotation @ MyAnno
}

@MyAnno
class A { // Parent class A is annotated by @ MyAnno annotation
}

public class B extends A { // Subclass B inherits from A, and B is not annotated
    public static void main(String[] args) {
        // Get all annotations for B annotation
        System.out.println(Arrays.toString(B.class.getAnnotations()));
    }
}

/* Operation results:
[@MyAnno()]
 */

According to the analysis results, although class B is not annotated, it inherits from class A. class A is annotated with @ Inherited meta annotation @ MyAnno, so class B is automatically annotated with @ MyAnno annotation.

4.5 @Repeatable

In JDK 1.8, Java provides the fifth meta annotation @ Repeatable. Annotations marked by meta annotation @ Repeatable are called Repeatable annotations, which can be repeated in the same place. It requires the following parameters:

parametertypeexplain
valueClass<? extends Annotation>Specifies the container annotation for storing duplicate labels

For example, a Person may play many roles in life, such as student, son, classmate, etc. write an @ Role annotation to mark the Person class:

@interface Role {
    String value();
}

@Role("student")
@Role("Son")
@Role("classmate")
public class Person {
}

However, the @ Role annotation cannot be used at the same location more than twice. When you try to compile, the compiler reports an error:

Before JDK 1.8, a container annotation is usually written to store the annotations that need to be repeated:

@interface Role {
    String value();
}

@interface Roles { // Container annotation
    Role[] value(); // @Role annotation array to store repeated @ role annotations
}

@Roles({@Role("student"), @Role("Son"), @Role("classmate")})
public class Person {
}

In JDK1.8, Java provides the meta annotation @ Repeatable. You only need to mark the @ Repeatable meta annotation on the @ Role annotation and specify the container annotation as @ Roles to reuse it:

@Repeatable(Roles.class) // Specifies the container annotation
@interface Role {
    String value();
}

@interface Roles { // Container annotation
    Role[] value(); // @Role annotation array to store repeated @ role annotations
}

@Role("student") // The @ Role annotation can be reused here
@Role("Son")
@Role("classmate")
public class Person {
}

be careful:

  • The retention rule of container annotation must be more relaxed than that of repeatable annotation (for example, the retention rule of repeatable annotation @ Role is RetentionPolicy.CLASS, and the retention rule of container annotation @ Roles can only be RetentionPolicy.CLASS or RetentionPolicy.RUNTIME, not RetentionPolicy.SOURCE);
  • The label position of the repeatable annotation must include the label position of the container annotation (for example, if the label position of the repeatable annotation @ Role is on a class or method, the label position of the container annotation @ Roles can only be on a class or method).
  • Container annotations and repeatable annotations are either annotated by @ Inherited meta annotations or are not annotated.

5. Preparation of custom annotations

The above chapters have involved the preparation of custom annotations, which are described in detail here.

Annotation is essentially an interface, which is defined by the character @ plus keyword interface:

public @interface MyAnno {
    
}

Define the member variable in the annotation to declare the method in the interface:

public @interface MyAnno {
    String str(); // String type member str
    int[] arr();  // int [] type member arr
}

// When using this annotation, its internal members are assigned:
// @MyAnno(str = "HelloWorld", arr = {1, 2, 3})

Note: the data type of the member variable in the annotation can only be one of the following:

  • Basic data types (int, double, etc.);
  • String type (string);
  • Class type (class <? >);
  • Enumeration type (< T extends enum < T > > t, i.e. enum type);
  • Annotation type (< T extends annotation > t, i.e. @ interface type);
  • Array types of the above types.

The default keyword can be used to set the default value of the member variable in the annotation:

public @interface MyAnno {
    String str() default "HelloWorld";
    int[] arr() default {1, 2, 3};
}

// When using this annotation, its internal members can be assigned. If not assigned, its value will use the default value

You can then use the five meta annotations provided by Java to constrain the custom annotations.

6. Reflection processing annotation

Most of the Java reflection classes used to describe the structure (such as Class class describing Class or interface, Constructor Class describing construction method, Field Class describing variable, etc.) implement the AnnotatedElement interface.
It is located under the java.lang.reflect package and is used to define the basic method of using reflection to process annotations:

methodreturn typefunction
getAnnotation(Class<T> annotationClass)<T extends Annotation> TReturns the specified annotations (including inherited annotations) that annotate this structure,
If it does not exist, null is returned
getAnnotations()Annotation[]Returns all annotations (including inherited annotations) that annotate this structure
getDeclaredAnnotation(Class<T> annotationClass)<T extends Annotation> TReturns the specified annotation that annotates this structure. If it does not exist, it returns null
getDeclaredAnnotations()Annotation[]Returns all annotations that annotate this structure
isAnnotationPresent(Class<? extends Annotation> annotationClass)booleanReturns whether the structure is annotated by the specified annotation. If yes, it returns true. If no, it returns false

For example, write an annotation @ Hello, and use the dynamic proxy implementation to output a welcome statement when calling the method annotated by @ Hello annotation.

Write @ Hello annotation:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Hello {
    
}

Write method interface hellloadable:

public interface Helloable {
    void foo();
    void foo2();
}

Write two interface implementation classes, and each class annotates different methods with @ Hello annotation:

class HelloClass implements Helloable {
    @Hello
    @Override
    public void foo() {
        System.out.println("Yes HelloClass Medium foo()method");
    }
    @Override
    public void foo2() {
        System.out.println("Yes HelloClass Medium foo2()method");
    }
}
class HelloClass2 implements Helloable {
    @Override
    public void foo() {
        System.out.println("Yes HelloClass2 Medium foo()method");
    }
    @Hello
    @Override
    public void foo2() {
        System.out.println("Yes HelloClass2 Medium foo2()method");
    }
}

Write the proxy factory class ProxyFactory and test it:

public class ProxyFactory {
    public static Object newProxyInstance(Object subject) {

        ClassLoader loader = subject.getClass().getClassLoader();
        Class<?>[] interfaces = subject.getClass().getInterfaces();
        InvocationHandler handler = methodEnhance(subject);

        return Proxy.newProxyInstance(loader, interfaces, handler);
    }
    private static InvocationHandler methodEnhance(Object subject) {
        return (proxy, method, args) -> {
            // Method to get the proxied object
            Method method1 = subject.getClass().getMethod(method.getName(), method.getParameterTypes());
            // Judge whether the method of the proxy object is annotated by @ Hello annotation
            if (method1.isAnnotationPresent(Hello.class)) {
                // If yes, the welcome statement is output
                System.out.println("HelloWorld");
            }
            return method.invoke(subject, args);
        };
    }
    public static void main(String[] args) {
        Helloable hello1 = (Helloable) ProxyFactory.newProxyInstance(new HelloClass());
        hello1.foo();
        hello1.foo2();
        Helloable hello2 = (Helloable) ProxyFactory.newProxyInstance(new HelloClass2());
        hello2.foo();
        hello2.foo2();
    }
}

Operation results:

HelloWorld
 Yes HelloClass Medium foo()method
 Yes HelloClass Medium foo2()method
 Yes HelloClass2 Medium foo()method
HelloWorld
 Yes HelloClass2 Medium foo2()method

After writing custom annotations, you must write the logic for handling annotations, otherwise the annotated annotations will be meaningless.

7. Other common annotations provided by JDK

The @ Override, @ Deprecated and @ SuppressWarnings annotations have been introduced in Chapter 2. This section introduces other common annotations provided by JDK.

7.1 @SafeVarargs

The compiler issues a warning when declaring constructors or methods with variable parameters of fuzzy types, such as Object types or generics.

If the user can determine that the method body will not cause unsafe operations on its variable parameters, the @ SafeVarargs annotation can be used to suppress compiler warnings. Similar is marked by @ SuppressWarnings("unchecked").

Write code to test:

public class TestSafeVarargs<T> {
    @SafeVarargs // Constructors use variable parameters of generic types
    public TestSafeVarargs(T... args) {
        for (T t : args) {
            System.out.println(t.toString());
        }
    }
    @SafeVarargs // Static methods use variable parameters of type Object
    public static void printVarargs(Object... args) {
        for (Object t : args) {
            System.out.println(t.toString());
        }
    }
    @SafeVarargs // The final method uses variable parameters of generic types
    public final void printVarargs1(T... args) {
        for (T t : args) {
            System.out.println(t.toString());
        }
    }
}

@The SafeVarargs annotation can only be used on methods or constructor methods with variable parameters, and the methods must be declared as static or final, otherwise compilation errors will occur. The premise of using @ SafeVarargs annotation for a method is that the user must ensure that the processing of generic type parameters in the implementation of this method will not cause type safety problems.

7.2 @FunctionalInterface and Lambda expression

In JDK 1.8, Java provides @ FunctionaInterface annotation to identify functional interfaces.

Functional interface refers to an interface with only one abstract method, but it can have multiple non abstract methods or abstract methods with default implementation.
The annotation @ FunctionalInterface can be marked on a functional interface so that the compiler can check whether it is a functional interface:

@FunctionalInterface
interface MyFunctionalInterface1 {
    void foo();
}

@FunctionalInterface
interface MyFunctionalInterface2 {
    void foo(String param);
}

Before JDK1.8, functional interfaces were mainly implemented in the form of anonymous inner classes:

public class Test {
    public static void main(String[] args) {
        MyFunctionalInterface1 interface1 = new MyFunctionalInterface1() {
            @Override
            public void foo() {
                System.out.println("Nonparametric functional interface");
            }
        };
        interface1.foo();
        MyFunctionalInterface2 interface2 = new MyFunctionalInterface2() {
            @Override
            public void foo(String param) {
                System.out.println(param);
            }
        };
        interface2.foo("Parametric functional interface");
    }
}

JDK1.8 introduces the concepts of annotation @ functional interface annotation and Lambda expression, so that the functional interface can use a more concise implementation method - Lambda expression:

public class Test {
    public static void main(String[] args) {
        MyFunctionalInterface1 interface1 = () -> System.out.println("Nonparametric functional interface");
        interface1.foo();
        MyFunctionalInterface2 interface2 = param -> System.out.println(param);
        interface2.foo("Parametric functional interface");
    }
}

For the functional interface of parameterless abstract method, Lambda expression is used to implement:

// If the method body has only one statement
() -> sentence;
// If the method body has multiple statements
() -> {
    // Statement block
};

For the functional interface of parametric abstract methods, Lambda expression is used to implement:

// If there is only one parameter
 Formal parameter -> sentence;
// If there are multiple parameters
(Formal parameter 1, Formal parameter 2, ...) -> sentence;

Users use Lambda expressions to simplify anonymous methods, but in some cases, we only use Lambda expressions to call some existing methods, and do nothing except calling these methods. In this case, the Lambda expression can be further simplified to a method reference. Use double colons (::).

There are four categories of method references:

reference typegrammarCorresponding Lambda expression
Static method referenceClass name:: staticMethod(parameter) - > class name. Staticmethod (parameter)
Instance method referenceObject name:: method(parameter) - > object name. Method (parameter)
Object method referenceClass name:: method(object name, parameter) - > class name. Method (parameter)
Construction method referenceClass name:: new(parameter) - > new class name (parameter)
  • Static method reference: if there is only one statement in the code block of Lambda expression and the statement calls the static method of a class, you can use double colons to replace it with static method reference:
@FunctionalInterface
interface I {
    void foo();
}
public class Test {
    public static void main(String[] args) {
        // Anonymous inner class writing
        I i1 = new I() {
            @Override
            public void foo() {
                System.gc();
            }
        };
        // Lambda expression
        I i2 = () -> System.gc();
        // Method reference writing
        I i3 = System::gc;
    }
}
  • Instance method reference: if there is only one statement in the code block of Lambda expression and the statement calls the member method of an object, you can use double colons to replace it with instance method reference:
@FunctionalInterface
interface I {
    void foo();
}
public class Test {
    public static void main(String[] args) {
        String str = new String();
        // Anonymous inner class writing
        I i1 = new I() {
            @Override
            public void foo() {
                str.toString();
            }
        };
        // Lambda expression
        I i2 = () -> str.toString();
        // Method reference writing
        I i3 = str::toString;
    }
}
  • Object method reference: if the first parameter of a Lambda expression is a method caller and the second parameter is a method parameter, you can use a double colon to replace it with an object method reference:
@FunctionalInterface
interface I {
    void foo(String a, String b);
}
public class Test {
    public static void main(String[] args) {
        // Anonymous inner class writing
        I i1 = new I() {
            @Override
            public void foo(String a, String b) {
                a.compareTo(b);
            }
        };
        // Lambda expression
        I i2 = (a, b) -> a.compareTo(b);
        // Method reference writing
        I i3 = String::compareTo;
    }
}
  • Constructor reference: if there is only one statement in the code block of Lambda expression and the statement calls the constructor of a class, you can use double colons to replace it with constructor reference:
    • The parameter list of the construction method to be called shall be consistent with the parameter list of the abstract method in the functional interface.
@FunctionalInterface
interface I {
    String foo(String a);
}
public class Test {
    public static void main(String[] args) {
        // Anonymous inner class writing
        I i1 = new I() {
            @Override
            public String foo(String a) {
                return new String(a);
            }
        };
        // Lambda expression
        I i2 = a -> new String(a);
        // Method reference writing
        I i3 = String::new;
    }
}

Keywords: Java Interview JavaSE Annotation

Added by Wireless102 on Wed, 01 Dec 2021 11:05:22 +0200