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:
parameter | type | explain |
---|---|---|
value | String[] | 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 type | explain |
---|---|
all | All warnings |
deprecation | Structure obsolescence warning |
rawtypes | Warning about using primitive types with generics |
serial | Serialization class did not provide serialVersionUID warning |
unchecked | Call primitive type structure warning |
unused | Structure 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:
method | return type | function |
---|---|---|
equals(Object obj) | boolean | Inherit from the Object class and compare whether it is the same as the Object obj |
hashCode() | int | Inherits from the Object class and returns the hash value of the current Object |
toString() | String | Inherits 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:
parameter | type | explain |
---|---|---|
value | RetentionPolicy | Retention 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:
parameter | type | explain |
---|---|---|
value | ElementType[] | Annotation location |
ElementType is an enumeration class located in the java.lang.annotation package. Its defined constants specify the annotation location:
constant | explain |
---|---|
ElementType.TYPE | Annotations can be marked on classes, interfaces (annotations) and enumeration classes |
ElementType.FIELD | Annotations can be annotated on member variables, including constants of enumeration classes |
ElementType.METHOD | Annotations can be marked on methods |
ElementType.PARAMETER | Annotations can be marked on the parameters of the method |
ElementType.CONSTRUCTOR | Annotations can be marked on the construction method |
ElementType.LOCAL_VARIABLE | Annotations can be annotated on local variables |
ElementType.ANNOTATION_TYPE | Annotations 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.PACKAGE | Comments can be marked on the package (used in package-info.java to output comments on the package when running javadoc command) |
ElementType.TYPE_PARAMETER | JDK1.8 provides that annotations can be marked on the declaration of generics |
ElementType.TYPE_USE | JDK1.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:
parameter | type | explain |
---|---|---|
value | Class<? 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:
method | return type | function |
---|---|---|
getAnnotation(Class<T> annotationClass) | <T extends Annotation> T | Returns 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> T | Returns 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) | boolean | Returns 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 type | grammar | Corresponding Lambda expression |
---|---|---|
Static method reference | Class name:: staticMethod | (parameter) - > class name. Staticmethod (parameter) |
Instance method reference | Object name:: method | (parameter) - > object name. Method (parameter) |
Object method reference | Class name:: method | (object name, parameter) - > class name. Method (parameter) |
Construction method reference | Class 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; } }