Java's Annotation interface

  • PS: This blog will involve some knowledge of Google Guice, but don't panic, even if you never know Guice, you can read this blog normally

1. Preamble

  • When learning Java annotations , it was mentioned that all annotations will inherit java.lang.annotation.Annotationthe interface and cannot inherit other classes or implementations
  • When really using annotations in depth, it is very necessary to find that the system learns the Annotation interface
  • For example, the Annotation interface defines its own equals(), hashCode()and toString()methods, and has its own set of rules for the implementation of these methods
  • Failure to rewrite related methods according to these rules may lead to problems with the use of annotation objects

2. Important methods of the Annotation interface

  • Carefully read the source code of the Annotation interface, the class annotations are as follows, the key information: all annotations will inherit the Annotation interface

    The common interface extended by all annotation types. Note that an interface that manually extends this one does not define an annotation type. Also note that this interface does not itself define an annotation type.

  • The Annotation interface defines its own methods, even including equals(), hashCode(), and toString() methods

    public interface Annotation {
          
          
        boolean equals(Object obj);
        int hashCode();
        String toString();
        Class<? extends Annotation> annotationType();
    }
    
  • The above three methods also exist in the Object class, but the two have different constraints on these three methods

2.1 toString () method

  • The toString() method of the Object class returns a string that can represent an object. The default implementation is:class_name@16进制_hashcode

    public String toString() {
          
          
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }
    

  • The toString() method of the Annotation interface returns a character representing the annotation

  • commonly used @annotation_class_name(memver1=value1,member2=value2, ...)format

  • Use Guice to customize binding annotations to define multiple elements@MultiMember

    @Retention(RUNTIME)
    @Target({
          
          ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
    @Qualifier
    public @interface MultiMember {
          
          
        String name();
    
        int version();
    }
    
  • The actual code of @MultiMember's toString() method is as follows:

    public String toString() {
          
          
        return "@" + MultiMember.class.getName() + "(" + Annotations.memberValueString("name", name) + ", "
                + Annotations.memberValueString("version", version) + ")";
    }
    
  • Using @MultiMember in @MultiMember(name="lucy", version=9)the way, Guice will print the annotation information when the program runs wrong:@org.sunrise.binding.MultiMemberBinding(name=lucy, version=9)

2.2 equals () method

  • Object stipulates that the equals() method is used to judge whether two objects are equivalent, and requires its implementation to have five characteristics: reflexivity, symmetry, transitivity, consistency, and non-emptiness

  • Object's equals() method is implemented, using the highest standard of object equality - the same object

    public boolean equals(Object obj) {
          
          
        return (this == obj);
    }
    
    

  • The equals() method of the Annotation interface is used to determine whether the objects of two annotation types are equal
  • The equals() method returns true: the specified object (obj) and the current object (this) belong to the same annotation type, and all elements (member variables) in the two objects are correspondingly equal,
  • How to compare elements for equality?
    • Basic data types, except float and double, use x == yto judge
    • The float and double types need to be judged by the packaging type, for example, the double type should be Double.valueOf(x).equals(Double.valueOf(y))judged by using
    • String, Class, enumeration, annotation type, use x.equals(y)to judge
    • Array type, use Arrays.equals(x, y)to judge

2.3 hashCode () method

  • The hashCode() method of the Object class returns the hash code of the object

  • The Object class requires the hashCode() method to be idempotent and abide by the following two conventions:

    • Two objects are equal, hash code is equal
    • The hash code is equal, and the two objects are not necessarily equal
  • The hashCode() method of the Object class is a native method that converts the object memory address into an integer to ensure that different objects have different hash codes

    public native int hashCode();
    

  • The hashCode() method of the Annotation interface returns the hash code of the annotation

  • The hash code of the annotation is the sum of the hash codes of each element in the annotation, namelysum(member_hashcode)

  • The hash code of each element:(127 * "member_name".hashCode()) ^ member_value_hashcode

    • member_name is the element name, which is of type String
    • The hash code of member_value depends on the type of element:
      • Basic data type, use its wrapper class to calculate hash code,WrapperType.valueOf(v).hashCode()
      • String, Class, enumeration, and annotation types, whose value is v, use v.hashCode().the hash code to calculate
      • Array type, call on its value Arrays.hashCode()to calculate hash code
  • The actual code of the hashCode() method of @MultiMember is as follows:

    public int hashCode() {
          
          
        return ((127 * "name".hashCode()) ^ name.hashCode())
                + ((127 * "version".hashCode()) ^ Integer.valueOf(version).hashCode());
    }
    
  • Notice:

    • When calculating the sum of the hash codes of multiple elements, be sure to add the calculation expression of the hash code of each element (), and then sum.
    • Otherwise, the calculated hash code does not meet the requirements of Annotation, which will cause whether the objects of the annotation type are equal, and the judgment fails

2.4 annotationType() method

  • In the Object class, there is no annotationType() method
  • The annotationType() method of the Annotation interface is used to return the type of annotation
  • The annotation is actually an interface, and the class can be defined to implement the annotation. The annotationType() method of these implementation classes generally returns the type of the annotation implemented
  • For example, MultiMemberImpl implements @MultiMember, and the returned annotation type is MultiMember
    @Override
    public Class<? extends Annotation> annotationType() {
          
          
        return MultiMember.class;
    }
    

3. implements annotation

  • In many scenarios, we just define annotations and then use them directly in some places.
  • To learn how to customize annotations , you can understand the definition of annotations according to the definition of interfaces. For example, the comparative study of the element declaration of the annotation and the method declaration in the interface
  • In addition, we don't have a deep understanding of annotations as an interface
  • This section will introduce how to implement annotations. On the one hand, you can learn how to implement annotations according to the requirements in Annotation. On the other hand, it will help you understand the fact that annotations are an interface.

3.1 Imperfect annotation implementation

3.1.1 IDE automatically introduces methods that need to be rewritten

  • Create MultiMemberImpla class as the implementation class of @MultiMember

  • At this time, the IDE will prompt that there is a method that needs to be rewritten

  • Introduce these methods that need to be rewritten through the IDE, and the formed code is as follows:

    public class MultiMemberImpl implements MultiMember{
          
          
        @Override
        public String name() {
          
          
            return null;
        }
    
        @Override
        public int version() {
          
          
            return 0;
        }
    
        @Override
        public Class<? extends Annotation> annotationType() {
          
          
            return null;
        }
    }
    
  • Looking at the bytecode of @MultiMember, we can see that the compiler automatically generates abstract getter methods for the two elements of @MultiMember

  • Therefore, the IDE automatically adds name()and version()methods for MultiMemberImpl to implement method rewriting

  • At the same time, according to the inheritance relationship, MultiMemberImpl also needs to rewrite the methods in the Annotation interface (four in total), but the IDE only introduces one of the methods for annotationType()rewriting

3.1.2 Why are other methods missing?

  • The remaining three methods in Annotation, equals(), hashCode(), and toString(), are just the same as the methods in the Object class
  • Object is the base class of all Java classes, and MultiMemberImpl will automatically inherit these three methods of Object
  • The three inherited methods just become the rewriting methods of the three methods in Annotation. The method has been rewritten, and the IDE will not automatically import these methods

3.1.3 Improve the logic of the rewriting method

  • Based on the above code, improve the logic of the rewriting method

    public class MultiMemberImpl implements MultiMember {
          
          
        private final String name;
        private final int version;
    
        public MultiMemberImpl(String name, int version) {
          
          
            this.name = name;
            this.version = version;
        }
    
        @Override
        public String name() {
          
          
            return this.name;
        }
    
        @Override
        public int version() {
          
          
            return this.version;
        }
    
        @Override
        public Class<? extends Annotation> annotationType() {
          
          
            return MultiMember.class;
        }
    }
    
  • Write the main() method to use MultiMemberImpl, and verify that MultiMemberImpl inherits the three methods of Object

    public static void main(String[] args) {
          
          
        MultiMemberImpl a = new MultiMemberImpl("jdk", 9);
        MultiMemberImpl b = a;
        MultiMemberImpl c = new MultiMemberImpl("jdk", 9);
        System.out.printf("a.toString(): %s\n", a);
        System.out.printf("a.equals(b): %b, a.equals(c): %b\n", a.equals(b), a.equals(c));
        System.out.printf("a.hash_code: %d, c.hash_code: %d\n", a.hashCode(), c.hashCode());
    }
    
  • The execution results are as follows, it can be found that equals(), hashCode() and toString() of MultiMemberImpl all inherit Object

3.1.4 Existing problems

  • Such a MultiMemberImpl is completely sufficient in the test scenario
  • In real application scenarios, because the equals(), hashCode() and toString() methods are not rewritten according to the regulations in Annotation, the normal execution of the program will be affected
  • For example, simulate the @Named implementation in Guice and customize a @Binding. If the above method is not rewritten correctly, @Binding(name="database") will not be able to match the Bindings.bind("database")defined binding
  • Interested readers can read "Google Guice 3: Bindings"

3.2 Correct implementation of annotations

  • In order to meet the real application scenario, it is necessary to rewrite the equals(), hashCode() and toString() methods according to the regulations in Annotation

    @Override
    public int hashCode() {
          
          
        return ((127 * "name".hashCode()) ^ name.hashCode())
                + ((127 * "version".hashCode()) ^ Integer.valueOf(version).hashCode());
    }
    
    @Override
    public boolean equals(Object obj) {
          
          
        if (!(obj instanceof MultiMember)) {
          
          
            return false;
        }
        MultiMember other = (MultiMember) obj;
        return this.name.equals(other.name()) && this.version == other.version();
    }
    
    @Override
    public String toString() {
          
          
        return "@" + MultiMember.class.getName() + "(" + Annotations.memberValueString("name", name) + ", "
                + Annotations.memberValueString("version", version) + ")";
    }
    
  • Re-execute the main() method, and the execution result changes

Guess you like

Origin blog.csdn.net/u014454538/article/details/129116171