Notes on java pitfalls

1. Introduction to annotations 

Annotation: Annotation

  • Introduced from JDK 1.5
  • Tags located in the source code (code/comments/annotations) and processed using other tools
  • Annotations are used to modify program elements, but will not have a direct impact on the modified object.
  • Annotation information can only be accessed and processed through certain supporting tools.

The main purpose

  • Provide information to compiler/IDE tools
  • Can be used with other tools to generate additional code/config files etc.
  • There are some annotations that can be accessed while the program is running, increasing the dynamics of the program.

 1.1 Common common annotations

1.2 Common meta-annotations

 1.3 Custom annotations

2. Java predefined common annotations

Java's predefined common annotations are annotations used to modify elements such as classes, methods, fields, etc. They can provide some compiler or runtime information, such as checking whether the overridden method is correct, marking obsolete elements, ignoring warning messages, etc. . The common annotations predefined by Java include the following:

  • @Override : Used to modify a method, indicating that the method overrides the method in the parent class or interface. If there is no matching parent class or interface method, the compiler will report an error.
  • @Deprecated : Used to modify elements such as classes, methods, fields, etc., indicating that the element is obsolete and is not recommended for use. If this element is used, the compiler will issue a warning.
  • @SuppressWarnings : Used to modify elements such as classes, methods, fields, etc. to indicate that specified compiler warnings are ignored. One or more warning types can be specified.
  • @SafeVarargs : A method or constructor used to modify variable parameters, indicating that the method or constructor will not perform unsafe operations on generic parameters and can ignore heap pollution warnings.
  • @FunctionalInterface : used to modify the functional interface, indicating that the interface has only one abstract method, which can be implemented using lambda expressions.

 2.1@Override

The @Override annotation is an annotation used to indicate method overriding. It allows the compiler to check whether the method of the subclass correctly overrides the method in the parent class or interface. If there is no matching parent class or interface method, the compiler An error will be reported. The @Override annotation can improve the readability and maintainability of the code, and can also avoid some potential errors.

Override the methods of the parent class in the subclass and use the @Override annotation to indicate

class Animal {
    public void eat() {
        System.out.println("Animal eats");
    }
}

class Dog extends Animal {
    @Override // 表示重写父类的eat方法
    public void eat() {
        System.out.println("Dog eats");
    }
}

 Override the interface method in the class that implements the interface and use the @Override annotation to indicate it

interface Shape {
    public void draw();
}

class Circle implements Shape {
    @Override // 表示重写接口的draw方法
    public void draw() {
        System.out.println("Draw a circle");
    }
}

2.2@Deprecated

The @Deprecated annotation is an annotation used to mark obsolete elements such as classes, methods, fields, etc. It allows the compiler to issue a warning when using this element, reminding developers not to use this element anymore, but to use new alternatives. plan

package org.example;

class Animal {
    @Deprecated
    public void makeSound() {
        System.out.println("This method is deprecated.");
    }

    public void communicate() {
        System.out.println("Animals communicate in various ways.");
    }
}

public class Test {
    public static void main(String[] args) {
        Animal animal = new Animal();

        // 使用已过时的方法,会生成警告
        animal.makeSound();

        // 使用新方法
        animal.communicate();
    }
}

2.3@SuppressWarnings

The @SuppressWarnings annotation is an annotation used to ignore specified compiler warnings. It can be used to modify elements such as classes, methods, fields, etc., and can specify one or more warning types.

  1. Suppress various types of warning messages so that the compiler does not display warnings
  2. Different types are superimposed, such as the warning type of modified classes and the warning types of modified methods. For methods, they are superimposed.
  3. Warning type names are determined by the compiler/IDE tool itself, and the Java specification does not mandate which names are required. Compiler vendors need to negotiate among themselves to ensure that warning types with the same name work equally well on each compiler.

package org.example;

import java.util.ArrayList;
import java.util.List;

public class SuppressWarningsExample {
    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        // 创建一个泛型列表,不指定泛型类型
        List myList = new ArrayList();
        myList.add("Item 1");
        myList.add("Item 2");

        // 使用 @SuppressWarnings 抑制未经检查的警告
        List<String> stringList = myList;

        // 不会再生成未经检查的警告
        System.out.println(stringList.get(0));
        System.out.println(stringList.get(1));
    }
}

 2.4@SafeVarargs

@SafeVarargs is an annotation used to suppress compiler warnings . It can be used to modify methods or constructors that use variable parameters (varargs), indicating that the method or constructor will not perform potentially unsafe operations on its variable parameters. Operations, such as converting it to a different type or returning it to the outside. The @SafeVarargs annotation can improve the readability and maintainability of the code, and can also avoid some potential errors.

The @SafeVarargs annotation was introduced in Java 7. Initially it could only be used to modify final or static methods or constructors, because these methods or constructors cannot be overridden, thus ensuring safety. But in Java 9, the @SafeVarargs annotation can also be used to modify private instance methods, because these methods cannot be overridden.

2.5@FunctionalInterface

@FunctionalInterface is an annotation used to mark functional interfaces. It indicates that the interface has only one abstract method, which can be implemented using lambda expressions or method references. Functional interface is a new feature introduced in Java 8, which can make the code more concise, clear and elegant. Some examples of functional interfaces are Runnable, ActionListener, Comparable, etc. The @FunctionalInterface annotation allows the compiler to check whether the interface conforms to the definition of a functional interface. If it does not conform, the compiler will report an error. The @FunctionalInterface annotation can also improve the readability of the code and clearly express the purpose of the interface. 

package org.example;



@FunctionalInterface // 表示这是一个函数式接口
interface Adder {
    int add(int a, int b); // 定义一个抽象方法add
}

public class Test {
    public static void main(String[] args) {
        Adder adder = (a, b) -> a + b; // 使用lambda表达式创建Adder接口的实例
        System.out.println(adder.add(10, 20)); // 调用add方法,输出30
    }
}

3. Custom annotations

 3.1 Types that annotations can include

 3.2 Usage steps

The basic process of customizing annotations is: first step, define annotations, use @interface syntax, and specify meta-annotations (annotation annotations) to configure the scope and strategy of the annotations; second step, use annotations to mark the annotations where needed in the program code used; the third step is to parse the annotations, detect the annotations during compilation or runtime, and perform special operations.

 3.3 Special

  • Annotations can use meta-annotations to specify their scope and policies. For example, @Target specifies which elements the annotation can be used on, @Retention specifies when the annotation is valid, @Documented specifies whether the annotation is included in the user document, and @Inherited specifies whether the annotation is included in the user document. Can be inherited by subclasses, @Repeatable specifies whether the annotation can be reused on the same element, for example:
@Target(ElementType.METHOD) // 表示该注解只能用于方法上
@Retention(RetentionPolicy.RUNTIME) // 表示该注解在运行时有效
@Documented // 表示该注解会被javadoc工具记录
@Inherited // 表示该注解可以被子类继承
@Repeatable(MyAnnotations.class) // 表示该注解可以重复使用,需要指定一个容器注解来存放重复的注解
public @interface MyAnnotation {
    // 注解内容
}

 3.4 Related code

Annotated code

package org.example;

import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
//表示该注解会保留在class文件中
@Target(ElementType.METHOD)
//表示该注解只能用于方法
public @interface MultipleTest {
    int a() default 0;
    int b() default 0;
}

test code

package org.example;

public class Foo {
    @MultipleTest(a=1,b=1)
    public static void m1(int a, int b) {
        if (a + b < 0) {
            throw new RuntimeException("Crash");
        }
    }

    @MultipleTest
    public static void m2(int a, int b) {
        //全部采用默认值
        if (a + b < 0) {
            throw new RuntimeException("Broken");
        }
    }
    @MultipleTest(b=-2,a=1)
    public static void m3(int a, int b) {
        //全部采用默认值
        if (a + b < 0) {
            throw new RuntimeException("Broken");
        }
    }
}

parse code

package org.example;

import java.lang.reflect.Method;

public class tes {
    public static void main(String[] args) throws Exception {
        int passed = 0, failed = 0; // 给passed变量赋值为0
        String className = "org.example.Foo";
        for (Method m : Class.forName(className).getMethods()) {
            if (m.isAnnotationPresent(MultipleTest.class)) {
                System.out.println(m.getName());
                MultipleTest st = m.getAnnotation(MultipleTest.class);
                try {
                    m.invoke(null,st.a(),st.b());
                    passed++;
                } catch (Throwable ex) {
                    System.out.printf("Test %s failed: %s %n", m, ex.getCause()); // 使用%符号代替$符号
                    failed++;
                }
            }
        }
        System.out.printf("passed:%d , failed:%d",passed,failed);
    }
}

4. Java predefined meta-annotations

Meta-annotation is an annotation used to describe other annotations. It can be used to specify the scope, retention policy, documentation, inheritance, and repeatability of annotations.

4.1@retention 

@Retention is a meta-annotation used to specify the retention policy of annotations. It indicates when the annotation is valid, such as source code, bytecode or runtime. It requires a parameter of type RetentionPolicy, which can only be one

The parameters of @Retention have the following three values:

  1. RetentionPolicy.SOURCE : Indicates that the annotation is only valid in the source code, the compiler will discard it and will not write it to the bytecode file.
  2. RetentionPolicy.CLASS : Indicates that the annotation is valid in both source code and bytecode. The compiler will write it to the bytecode file, but the virtual machine will not read it. This is the default value .
  3. RetentionPolicy.RUNTIME: Indicates that the annotation is valid in source code, bytecode and runtime. The compiler will write it to the bytecode file and the virtual machine will read it. It can be obtained through the reflection mechanism.
package org.example;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME) // 指定注解在运行时保留
public @interface AnimalInfo {
    String species() default "Unknown"; // 动物的种类属性,默认值为"Unknown"
    int age() default 0; // 动物的年龄属性,默认值为0
}
package org.example;

@AnimalInfo(species = "Generic Animal", age = 1) // 使用注解为这个动物类提供信息
public class Animal {
    private String name;

    public Animal(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

public class AnimalInfoExample {
    public static void main(String[] args) {
        // 获取Animal类的Class对象
        Class<Animal> animalClass = Animal.class;

        // 获取Animal类上的所有注解
        Annotation[] annotations = animalClass.getAnnotations();
        for (Annotation annotation : annotations) {
            if (annotation instanceof AnimalInfo) {
                AnimalInfo animalInfo = (AnimalInfo) annotation;
                String species = animalInfo.species();
                int age = animalInfo.age();
                System.out.println("Species: " + species);
                System.out.println("Age: " + age);
            }
        }
    }
}

4.2 @Target

@Target is a meta-annotation used to specify which elements the annotation can be used on. It indicates the scope of the annotation, such as classes, methods, fields, parameters, etc. It requires a parameter of type ElementType, which can be one or more

The parameters of @Target have the following values:

  • ElementType.TYPE: Indicates that annotations can be used for classes, interfaces, enumerations or annotation types.
  • ElementType.FIELD: Indicates that annotations can be used for fields or enumeration constants.
  • ElementType.METHOD : Indicates that annotations can be used for methods.
  • ElementType.PARAMETER: Indicates that annotations can be used for method parameters.
  • ElementType.CONSTRUCTOR: Indicates that annotations can be used in constructors.
  • ElementType.LOCAL_VARIABLE: Indicates that annotations can be used for local variables.
  • ElementType.ANNOTATION_TYPE: Indicates that annotations can be used for annotation types.
  • ElementType.PACKAGE: Indicates that annotations can be used for package declarations.
  • ElementType.TYPE_PARAMETER: Indicates that annotations can be used for type parameters, which is a new feature of Java 8.
  • ElementType.TYPE_USE: Indicates that the annotation can be used for any type of use, which is also a new feature of Java 8
import java.lang.annotation.*;

@Target({ElementType.TYPE, ElementType.METHOD}) // 指定注解可以应用于类和方法
@Retention(RetentionPolicy.RUNTIME) // 指定注解在运行时保留
public @interface AnimalInfo {
    String species() default "Unknown"; // 动物的种类属性,默认值为"Unknown"
    int age() default 0; // 动物的年龄属性,默认值为0
}

4.3@Inherited

@Inherited is a meta-annotation used to indicate that parent class annotations will be inherited by subclasses. It indicates that the annotation can be inherited by subclasses. It has no parameters.

The function of @Inherited is to make custom annotations inheritable, that is, if a class uses a certain annotation, its subclasses will automatically have the annotation without the need to annotate it again. This simplifies the code and avoids repeated annotations. @Inherited is only valid for class annotations, not for method and attribute annotations.

import java.lang.annotation.*;

@Inherited // 指示该注解可以被继承
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface CustomAnnotation {
    String value() default "Default Value";
}
@CustomAnnotation(value = "Annotated Parent Class")
public class Animal {
    private String name;

    public Animal(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}
public class Cat extends Animal {
    public Cat(String name) {
        super(name);
    }
}
import java.lang.annotation.Annotation;

public class InheritedAnnotationExample {
    public static void main(String[] args) {
        // 获取 Cat 类的 Class 对象
        Class<Cat> catClass = Cat.class;

        // 获取 Cat 类上的所有注解,包括继承的注解
        Annotation[] annotations = catClass.getAnnotations();
        for (Annotation annotation : annotations) {
            if (annotation instanceof CustomAnnotation) {
                CustomAnnotation customAnnotation = (CustomAnnotation) annotation;
                String value = customAnnotation.value();
                System.out.println("Annotation Value: " + value);
            }
        }
    }
}

4.4@Documented

@Documented is a meta-annotation used to include annotations in javadoc, which indicates that the annotations will be recorded by the javadoc tool. It has no parameters.

The function of @Documented is to allow custom annotations to be displayed when generating documents instead of being ignored. This can improve the readability and maintainability of the code, and also make it easier for others to understand the meaning and usage of the annotations. @Documented is only valid for class annotations, not for method and attribute annotations.

import java.lang.annotation.*;

@Documented // 指示编译工具生成文档时包括此注解信息
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface AnimalInfo {
    String species() default "Unknown"; // 动物的种类属性,默认值为"Unknown"
    int age() default 0; // 动物的年龄属性,默认值为0
}

4.5@Repeatable

@Repeatable is a meta-annotation used to indicate that annotations can be reused on the same element. It requires specifying a container annotation to store repeated annotations. The @Repeatable annotation can make the code more concise and clear, and can also avoid some potential errors. The @Repeatable annotation was introduced in Java 8. Initially it could only be used to modify final or static methods or constructors. However, in Java 9, the @Repeatable annotation can also be used to modify private instance methods.

package org.example;

import java.lang.annotation.*;

// 自定义注解 @RepeatableAnnotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Repeatable(RepeatableAnnotations.class) // 指示该注解可以多次应用于同一目标元素,RepeatableAnnotations.class 是容器注解
public @interface RepeatableAnnotation {
    int a() default 0;

    int b() default 0;

    int c() default 0;
}
package org.example;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// 容器注解 @RepeatableAnnotations
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RepeatableAnnotations {
    RepeatableAnnotation[] value(); // 一个数组属性,用于存储多个 @RepeatableAnnotation 注解
}
package org.example;

import java.lang.annotation.*;

// 示例类 Student
public class Student {
    // 使用 @RepeatableAnnotation 注解并检查条件
    @RepeatableAnnotation(a = 2, b = 3, c = 5) // 第一个 @RepeatableAnnotation 注解
    @RepeatableAnnotation(a = 4, b = 8, c = 11) // 第二个 @RepeatableAnnotation 注解
    public static void add(int a, int b, int c) {
        if (c != a + b) {
            throw new ArithmeticException("Wrong");
        }
        System.out.println("Sum: " + c);
    }

    public static void main(String[] args) throws Exception {
        String className = "org.example.Student";
        for (java.lang.reflect.Method m : Class.forName(className).getMethods()) {
            if (m.isAnnotationPresent(RepeatableAnnotations.class)) {
                RepeatableAnnotation[] annos = m.getAnnotationsByType(RepeatableAnnotation.class);
                for (RepeatableAnnotation anno : annos) {
                    System.out.println("a: " + anno.a() + ", b: " + anno.b() + ", c: " + anno.c());
                    try {
                        m.invoke(null, anno.a(), anno.b(), anno.c());
                    } catch (Throwable ex) {
                        System.out.printf("Test %s failed: %s%n", m, ex.getCause().getMessage());
                    }
                }
            }
        }
    }
}

5. Analysis of annotations 

The resolution of the solution refers to obtaining the annotation information on the program element through the reflection mechanism, and performing corresponding processing according to the attribute value of the annotation.

Parsing of Java annotations

  • The RetentionPolicy.RUNTIME annotation is parsed using reflection
  • The RetentionPolicy.CLASS annotation is parsed using a dedicated bytecode tool.
  • The RetentionPolicy.SOURCE annotation is parsed using an annotation processor.

The annotation processor inherits AbstractProcessor and overrides the process method
javac -processor Processor1, Processor2, … sourceJavaFile

 6. The implementation essence of RUNTIME annotation

6.1 Steps

In order to obtain and process RUNTIME annotations, we need the following steps:

  1. First, we need to use the forName method of the Class class or the getClass method of the object to obtain the Class object to which the target element belongs.
  2. Then, we need to use the getMethods, getFields, getConstructors and other methods of the Class object to obtain the Method, Field, Constructor and other objects corresponding to the target element.
  3. Next, we need to use the isAnnotationPresent method of Method, Field, Constructor and other objects to determine whether the specified RUNTIME annotation exists on the target element.
  4. Finally, we need to use the getAnnotation method or getAnnotationsByType method of Method, Field, Constructor and other objects to obtain the RUNTIME annotation instance on the target element, read the attribute value of the annotation, and then process it accordingly.

6.2 Add proxy class export settings

The specific methods of RUNTIME annotation proxy class export settings are as follows:

  • Use the System.setProperty method in the program to set system properties, for example:
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); // 设置保存代理类
  • Use the -D parameter on the command line to set system properties, for example:
java -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true Main // 设置保存代理类

If the RUNTIME annotation is loaded by the JVM, Java can use reflection to obtain the annotation content.

  1. The program automatically generates a proxy class for the annotation to intercept the annotation method access.
  2. The proxy class has an AnnotationInvocationHandler member variable, which stores all annotation assignments internally.

 7. Application of annotations

7.1Servlet configuration

7.2 Trains

 7.3Spring&&Spring Boot

7.4 Lombok 

Guess you like

Origin blog.csdn.net/qq_62377885/article/details/132841571