Java的Annotation接口

  • PS: 该博客将涉及Google Guice的一些知识,但不要惊慌,即使你从未了解过Guice,也能正常阅读该博客

1. 序言

  • 学习Java注解时,曾提到:所有的注解都将继承java.lang.annotation.Annotation接口,无法再继承其他的类或实现
  • 当真正深入使用注解时,发现系统学习Annotation接口是非常必要的
  • 例如,Annotation接口定义了自己的equals()hashCode()toString()方法,且对这些方法的实现有着自己的一套规则
  • 若不按照这些规则重写相关方法,则可能导致注解对象的使用存在问题

2. Annotation接口的重要方法

  • 仔细阅读Annotation接口的源码,类注释如下,关键信息:所有的注解都将继承Annotation接口

    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.

  • Annotation接口定义了自己的方法,甚至包括equals()、hashCode()和toString()方法

    public interface Annotation {
        boolean equals(Object obj);
        int hashCode();
        String toString();
        Class<? extends Annotation> annotationType();
    }
    
  • 上述三个方法,在Object类中也存在,但是二者对这三个方法的约束有所差异

2.1 toString()方法

  • Object类的toString()方法,返回一个可以表示对象的字符串,默认实现:class_name@16进制_hashcode

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

  • Annotation接口的toString()方法,返回一个表示注解的字符

  • 一般采用@annotation_class_name(memver1=value1,member2=value2, ...)的格式

  • 使用Guice自定义绑定注解的方式,定义含有多个元素的@MultiMember

    @Retention(RUNTIME)
    @Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
    @Qualifier
    public @interface MultiMember {
        String name();
    
        int version();
    }
    
  • @MultiMember的toString()方法的实际代码如下:

    public String toString() {
        return "@" + MultiMember.class.getName() + "(" + Annotations.memberValueString("name", name) + ", "
                + Annotations.memberValueString("version", version) + ")";
    }
    
  • @MultiMember(name="lucy", version=9)以的方式使用@MultiMember,Guice在程序运行出错时,将打印的注解信息为:@org.sunrise.binding.MultiMemberBinding(name=lucy, version=9)

2.2 equals()方法

  • Object规定equals()方法用于判断两个对象是否等价,要求其实现具备5大特性:自反性、对称性、传递性、一致性、非空性

  • Object的equals()方法实现,采用了对象相等的最高标准 —— 同一对象

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

  • Annotation接口的equals()方法,用于判断两个注解类型的对象是否相等
  • equals()方法返回true:指定对象(obj)与当前对象(this)属于相同的注解类型,且两个对象中的所有元素(成员变量)分别对应相等,
  • 如何比较元素是否相等?
    • 基本数据类型,除float、double外,使用 x == y进行判断
    • float、double类型,需要通过包装类型进行判断,例如,double类型,使用Double.valueOf(x).equals(Double.valueOf(y))进行判断
    • String、Class、枚举、注解类型,使用x.equals(y)进行判断
    • Array类型,使用 Arrays.equals(x, y)进行判断

2.3 hashCode()方法

  • Object类的hashCode()方法,返回对象的hash code

  • Object类要求hashCode()方法具备幂等性,遵守如下两个约定:

    • 两个对象相等,hash code相等
    • hash code相等,两个对象不一定相等
  • Obejct类的hashCode()方法,是一个native方法,它将对象内存地址转换为一个整数,保证不同的对象拥有不同的hash code

    public native int hashCode();
    

  • Annotation接口的hashCode()方法,返回注解的hash code

  • 注解的hash code,是注解中每个元素的hash code之和,即sum(member_hashcode)

  • 每个元素的hash code:(127 * "member_name".hashCode()) ^ member_value_hashcode

    • member_name是元素名,为String类型
    • member_value的hash code,取决于元素的类型:
      • 基本数据类型,使用其包装类计算hash code,WrapperType.valueOf(v).hashCode()
      • String、Class、枚举、注解类型,其值为v,则使用v.hashCode().计算hash code
      • Array类型,对其值调用Arrays.hashCode()以计算hash code
  • @MultiMember的hashCode()方法的实际代码如下:

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

    • 计算多个元素的hash code之和时,一定要为每个元素的hash code的计算表达式加(),再求和。
    • 否则,计算出的hash code不满足Annotation的要求,会导致注解类型的对象是否相等,判断失败

2.4 annotationType()方法

  • Object类中,没有annotationType()方法
  • Annotation接口的annotationType()方法,用于返回注解的类型
  • 注解实际也是一个接口,可以定义类实现注解。这些实现类的annotationType()方法,一般返回所实现的注解的类型
  • 例如,MultiMemberImpl实现了@MultiMember,返回的注解类型为MultiMember
    @Override
    public Class<? extends Annotation> annotationType() {
        return MultiMember.class;
    }
    

3. implements注解

  • 很多场景,我们只是定义注解,然后在某些地方直接使用注解。
  • 学习如何自定义注解,可以按照接口的定义方式理解注解的定义方式。例如,注解的元素声明与接口中方法声明的对照学习
  • 除此之外,我们对注解是一个接口的体会并不深
  • 本小节将介绍如何implements注解,一方面可以学习如何按照Annotation中的要求implements注解,另一方面有利于体会注解是一个接口这一事实

3.1 不完善的注解实现

3.1.1 IDE自动引入需要重写的方法

  • 创建MultiMemberImpl类,作为@MultiMember的实现类

  • 这时,IDE会提示有需要重写的方法
    Java的Annotation接口_第1张图片

  • 通过IDE引入这些需要重写的方法,形成的代码如下:

    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;
        }
    }
    
  • 查看@MultiMember的字节码可以知道,编译器自动为@MultiMember的两个元素生成了抽象的getter方法
    Java的Annotation接口_第2张图片

  • 因此, IDE为MultiMemberImpl自动添加了name()version()方法,以实现方法重写

  • 同时,根据继承关系可知,MultiMemberImpl还需要重写Annotation接口中的方法(共四个),IDE却只引入了其中一个方法annotationType()进行重写

3.1.2 为何缺少其他方法?

  • Annotation中剩余的三个方法,equals()、hashCode()和toString(),刚好与Object类中的方法相同
  • Object是所有Java类的基类,MultiMemberImpl将自动继承Object的这个三个方法
  • 继承而来的三个方法,刚好成为Annotation中三个方法的重写方法。方法已经被重写,IDE也就不会自动引入这些方法了

3.1.3 完善重写方法的逻辑

  • 基于以上代码,完善重写方法的逻辑

    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;
        }
    }
    
  • 编写main()方法以使用MultiMemberImpl,同时验证MultiMemberImpl继承了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());
    }
    
  • 执行结果如下,可以发现MultiMemberImpl的equals()、hashCode()和toString()均是继承了Object

3.1.4 存在的问题

  • 这样的MultiMemberImpl,在测试场景下完全够用
  • 在真实应用场景下,由于未按照Annotation中的规定重写equals()、hashCode()和toString()方法,将影响程序的正常执行
  • 例如,模拟Guice中的@Named实现,自定义一个@Binding,若不正确重写上述方法,@Binding(name=“database”)将无法匹配到Bindings.bind("database")定义的binding
  • 感兴趣的读者,可以阅读《Google Guice 3:Bindings》

3.2 正确的注解实现方式

  • 为满足真实应用场景,需要按照Annotation中的规定重写equals()、hashCode()和toString()方法

    @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) + ")";
    }
    
  • 重新执行main()方法,执行结果有所变化

你可能感兴趣的:(#,Java基础,java)