Java的Annotation接口

news/2024/4/29 21:01:27/文章来源:https://blog.csdn.net/u014454538/article/details/129116171
  • 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会提示有需要重写的方法

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

    public class MultiMemberImpl implements MultiMember{@Overridepublic String name() {return null;}@Overridepublic int version() {return 0;}@Overridepublic Class<? extends Annotation> annotationType() {return null;}
    }
    
  • 查看@MultiMember的字节码可以知道,编译器自动为@MultiMember的两个元素生成了抽象的getter方法

  • 因此, 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;}@Overridepublic String name() {return this.name;}@Overridepublic int version() {return this.version;}@Overridepublic 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()方法,执行结果有所变化

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_260715.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android自动化测试(UiAutomator)——UiObject

本文主要讲解使用UiAutomator的一些技巧&#xff0c;希望对于初学者有一定的帮助 UiObject 1、首先要声明对象 UiObject XXX new UiObject(new Selector) ; 2、其次对对象进行操作 操作类型包括&#xff1a; 执行类&#xff1a;文本输入与清除、点击/长按、拖动/滑动、 …

安装配置RabbitMQ(Win11)

一、安装依赖Erlang打开RabbitMQ官网&#xff1a;https://www.rabbitmq.com/点击Get Started点击Download Installation点击 Chocolatey or Installer点击? Erlang/OTP Version Tree点击win64下载完成后&#xff0c;右击“以管理员身份”安装配置Erlang环境变量 &#xff1a;…

Mybatis的介绍和基本使用

目录 数据库操作框架的历程 JDBC Hibernate JDBCTemplate 什么是Mybatis 快速搭建Mybatis项目 创建普通的maven项目 导入相关的依赖 创建对应的数据表 创建与表对应的实体类对象 创建对应的Mapper接口 编写配置文件 编写测试类 增删改查的基本操作 数据库操作框…

Java线程的6 种状态

Java 线程的状态 Java线程有六种状态&#xff1a; 初始&#xff08;NEW&#xff09;、运行&#xff08;RUNNABLE&#xff09;、阻塞&#xff08;BLOCKED&#xff09;、 等待&#xff08;WAITING&#xff09;、超时等待&#xff08;TIMED_WAITING&#xff09;、终止&#xff08…

vue-router 的基本用法

vue-router 的基本用法 1.什么是 vue-router vue-router 是 vue.js 官方给出的路由解决方案。它只能结合 vue 项目进行使用&#xff0c;能够轻松的管理 SPA 项目中组件的切换。 vue-router 的官方文档地址&#xff1a;https://router.vuejs.org/zh/ 2.vue-router 安装和配置的…

MacBook IDEA 顶部菜单栏不显示问题

文章目录背景&#xff1a;当前显示方式一1. 快捷键&#xff1a;双击shift 搜索 idea.vmoptions3. 在idea.vmoptions文件末尾添加 -Dapple.laf.useScreenMenuBarfalse方式二1. 访达 > 应用程序 > idea 右键 显示包内容2. 进入到bin包位置的命令终端3. 编辑文件 vi idea.pr…

EasyExcel 低内存导出大数据量的Excel方案探索 50万行 50列 (附:实现代码)

文章目录1.前言2.准备工作3.导出测试3.1.单次查询、全量导出3.2. 多次查询&#xff0c;多个文件&#xff0c;单次写入3.3.多次查询&#xff0c;多个文件&#xff0c;多次写入3.4.多线程导出探索3.5.文件打包成ZIP3.6.响应给客户4.实现代码5.结语1.前言 最近接到一个需求&#…

多元化增长引擎业务占比超四成,联想开启混动模式?

一句话概括联想集团的业绩&#xff1a;预料之内的整体下滑&#xff0c;超出预期的第二曲线。 上周五&#xff08;2月17日&#xff09;&#xff0c;联想集团发布了2022到2023财年第三季度业绩。根据财报&#xff0c;联想集团实现营收152.67亿美元&#xff0c;同比下降24%&#…

【机器学习】决策树-Gini指数

1. CART树 分类回归树(CART,Classification And Regression Tree)算法是一种决策树分类方法。CART每一个节点上都采用二分法&#xff0c;采用一种二分递归分割的技术&#xff0c;CART生成的树必须是二叉树&#xff0c;也就是无论回归还是分类&#xff0c;无论特征离散还是连续&…

15-基础加强3-单元测试日志

文章目录1.单元测试1.1概述【理解】1.2特点【理解】1.3使用步骤【应用】1.4相关注解【应用】2.日志2.1概述【理解】2.2日志体系结构和Log4J【理解】2.3入门案例【应用】1.单元测试 1.1概述【理解】 JUnit是一个 Java 编程语言的单元测试工具。JUnit 是一个非常重要的测试工具…

Profinet转ModbusTCP网关连接昆仑通态触摸屏配置案例

本案例是模拟将Modbus TCP 设备数据接入到西门子PROFINET 网络中。 使用设备为西门子 S7-1500 型 PLC, Profinet转ModbusTCP网关。MODBUS 从站昆仑通态触摸屏。 配置方法&#xff1a; 打开博图&#xff0c;新建项目并添加站点。 添加1513PLC。 设置好IP并处于联网状态 导入Pr…

VTK学习笔记(四十)vtk提取单层数据

VTK学习笔记&#xff08;四十&#xff09;vtk提取单层数据1. VTK学习笔记&#xff08;四十&#xff09;vtk提取单层数据1.1 vtkImageReslice提取单层图像1.2 vtkExtractVOI提取单层图像1. VTK学习笔记&#xff08;四十&#xff09;vtk提取单层数据 VTK实现单层图片的提取方法有…

力扣mysql刷题记录

mysql刷题记录 刷题链接https://leetcode.cn/study-plan/sql/?progressjkih0qc mysql冲&#xff01;mysql刷题记录一. 1699. 两人之间的通话次数题解二、1251. 平均售价题解三. 1571. 仓库经理题解四.1445. 苹果和桔子解五.1193. 每月交易 I题解六.1633. 各赛事的用户注册率题…

Android Spider XX兔 Wechat Cookie 校验 注册案例(二)

声明 此次案例只为学习交流使用&#xff0c;抓包内容、敏感网址、数据接口均已做脱敏处理&#xff0c;切勿用于其他非法用途&#xff1b; 文章目录声明前言一、资源推荐二、任务说明三、App抓包分析四、还原JS加密1、Date类2、MD5类3、组合调用还原 api_token 参数4、execjs模…

Android 基础知识4-2.10 GridLayout(网格布局)详解

一、GridLayout&#xff08;网格布局&#xff09;概述 GridLayout 布局是 Android 4.0 以后引入的新布局&#xff0c;和 TableLayout(表格布局) 有点类似&#xff0c;不过它功能更多&#xff0c;也更加好用&#xff0c;最大的特点是放置的组件自动占据网格的整个区域,每个组件的…

《FPGA学习》->呼吸灯

&#x1f34e;与其担心未来&#xff0c;不如现在好好努力。在这条路上&#xff0c;只有奋斗才能给你安全感。你若努力&#xff0c;全世界都会为你让路。呼吸灯&#xff0c;简而言之就像人类呼吸一样&#xff0c;有节奏的让LED灯从&#xff1a;灭->微微亮->微亮->亮-&g…

K_A12_014 基于STM32等单片机驱动S12SD紫外线传感器模块 串口与OLED0.96双显示

K_A12_014 基于STM32等单片机驱动S12SD紫外线传感器模块 串口与OLED0.96双显示一、资源说明二、基本参数参数引脚说明三、驱动说明IIC地址/采集通道选择/时序对应程序:数据对比&#xff1a;四、部分代码说明1、接线引脚定义1.1、STC89C52RCS12SD紫外线传感器模块1.2、STM32F103…

Leetcode.2257 统计网格图中没有被保卫的格子数

题目链接 Leetcode.2257 统计网格图中没有被保卫的格子数 Rating &#xff1a; 1709 题目描述 给你两个整数 m和 n表示一个下标从 0开始的 m x n网格图。同时给你两个二维整数数组 guards和 walls&#xff0c;其中 guards[i] [rowi, coli]且 walls[j] [rowj, colj]&#xff…

Jmeter常用断言之BeanShell断言详解

BeanShell断言可以使用beanshell脚本来执行断言检查&#xff0c;可以用于更复杂的个性化需求&#xff0c;使用更灵活&#xff0c;功能更强大&#xff0c;但是要能够熟练使用beanshell脚本 在这里除了可以使用beanshell的内置变量外&#xff0c;主要通过 Failure 和 FailureMess…

Dart中的混入类mixin

介绍 Mixin 是一种在多重继承中复用某个类中代码的方法模式。 Mixin 是面向对象程序设计语言中的类&#xff0c;提供了方法的实现。其他类可以访问mixin类的方法、变量而不必成为其子类。 简单来说就是官方设计了一个种可以方便复用的类&#xff0c;不必去实现很多接口。 应…