2025年《回炉重造》——注解

《回炉重造》——注解注解 前言 以前学习到 注解 的时候 没有好好理解注解是如何工作的 只是知道注解可以实现一些功能 总而言之 就是懵懵懂懂 不过 即使你不知道什么是注解 但肯定接触过注解 比如方法的重写 在方法上面写着 Override 这个东西就是注解

大家好,我是讯享网,很高兴认识大家。

注解

前言

以前学习到「注解」的时候,没有好好理解注解是如何工作的,只是知道注解可以实现一些功能,总而言之,就是懵懵懂懂。

不过,即使你不知道什么是注解,但肯定接触过注解,比如方法的重写,在方法上面写着 @Override,这个东西就是注解。

好了,下面就开始回炉重造!打好基础!

注解
讯享网

什么是注解?

注解(Annotation),Annotation 的意思有「标注、批注、附注」。

注解和泛型一样,也是从 JDK5 引入的新特性。它是与类、接口处在同一级别的,有它的语法来定义注解的

注解,人们还有另一个词来称呼它,即「元数据」,所谓元数据(metadata),就是用来描述数据的数据(data about data)。初看这句话可能会有点懵,不过没关系,都是这样过来的,好好细品细品。

品不过来?可以看看这里:

什么是元数据?为何需要元数据? - 贺易之的回答 - 知乎

元数据(MetaData)- 阮一峰

好了,我们知道「注解==元数据」,那么它描述什么数据呢?问得好,描述的就是我们写的源代码!可以描述类、方法、局部变量、方法参数等数据(这些数据也有另一个叫法,Java Element)。注意上面我说过 Annotation 的意思有 附注 的意思,是吧,所以说这个注解实际上并不是程序本身,只是向编译器提供相关的程序的附加信息,也就是说注解并不会影响程序的运行。

为什么会有注解的出现?

这就扯到历史的发展了,综合我所了解的,大概就是这样子:

这里也扯到了 XML(Extensible Markup Language),如果不熟悉,我这里就简单说下,熟悉可以跳过啦!

XML 翻译过来就是「可扩展的标记语言」,与 HTML 类似。但是 XML 是被设计用来传输数据的,并不是显示数据,具有「自我描述性」。

下面是 Jayson Tatum 写给 Kobe Bryant 的短信,存储为 XML:

<text-message> <to>Kobe Bryant</to> <from>Jayson Tatum</from> <body>I got you today</body> </text-message> 

讯享网

上面的短信就具有自我描述性。它拥有留言,同时包含了发送者和接受者的信息。当然,这仅仅是描述而已,XML 并没有做任何事情,就是纯文本,我们需要写代码,让代码识别这些标签,赋予意义,这样程序才能读懂 XML,知道它描述的是什么。

目前 XML 有两个作用,一是可以用 XML 格式来传输数据,二是可以作为配置文件。

在注解出现之前,开发者基本是用 XML 来配置某些东西,因为 XML 和代码是低耦合的,符合低耦合的需求,但是,随着从 XML 描述的数据越来越多,配置越来越复杂,人们发现用 XML 描述数据太复杂了,需要一种高耦合的来降低这种复杂的情况,所以才出现注解,用注解来描述数据。

不过,现在也是看情况,并不是说注解代替了 XML,而是有时使用 XML,有时使用注解,各有各的好处,两者相互结合等,这还需要具体情况具体分析。

为什么需要学习注解?

  1. 很显然,我们现在日常开发,使用到各种开源框架,经常会用到注解,不学习注解,那自然看不懂注解相关的代码。
  2. 看起来比较厉害

JDK 自带的注解

  • @Override :表示当前方法覆盖了父类的方法
  • @Deprecated: 表示该方法由于安全、性能问题等,已经不推荐使用了,即已经过时,方法上有横线,使用时会有警告。此外,在版本升级时,如果要计划删除一些方法,也通常会在前一个版本中,将该方法加上@Deprecated,然后再在后续版本中删除。
  • @SuppviseWarnings(value="unchecked"): 表示镇压警告信息(让编译器忽略特定的编译警告)

    一些 value 值:

    • uncheckeddeprecation(忽略 一些过期的 API 警告)
    • unused(忽略 没有被使用过的代码的警告)
    • fallthrough( 忽略 switch 中缺失 break 的警告)
    • all(忽略全部)

在 JDK 7 和 8,分别加入了 @SafeVarargs@FunctionalInterface 这两个注解。这两个后续再来填坑吧。

@Override有什么用?不加行不行?

@Override 是我们最早接触的注解了,不加它行不行?

行!不加也行!只要重写了方法正确就可以了,要是写错了,举个例子:

讯享网public class Pet { 
    public void eat() { 
    System.out.println("吃..."); } } 
public class Cat extends Pet{ 
    public void aet() { 
    System.out.println("吃鱼..."); } } 

这里 Cat 继承了 Pet,重写 eat() 方法,但是方法名写错了,写成 aet()

讯享网Pet pet = new Cat(); pet.eat(); // 父类引用调用子类方法 

由于子类重写的方法写错方法名,那么此时父类引用调用子类方法时,就找不到子类的 eat() 这个方法,然后就只能回去父类找,这显然不是我们想要的。

所以加上 @Override 有好处,写错方法名会有提示,也就是说,可以确保重写的方法,的确存在于父类/接口中,可以有效的避免单词拼错等情况。

注解的分类

有两种分类方式:

  • 按照运行机制分类
  • 按照来源分类

注解的分类

按照运行机制分类(何时保留,何时不保留,某个时期保留,某个时期不保留,生命周期,Retention)

  • SOURCE——源码注解:注解只在源码中存在,编译成.class文件就不存在了
  • CLASS——编译时注解:注解在源码和 .class文件 中都存在(如:JDK 自带注解)
  • RUNTIME——运行时注解:在运行阶段还起作用,甚至会影响运行逻辑的注解(如:Spring中@Autowired

按照来源分类

  • 元注解:给注解进行注解,也就是来修饰注解的,有4个;这里的元注解就好比上面说过的元数据,如果还是不能很好的理解的话,就把它理解成形容词
    • @Target({ElementType.METHOD, ElementType.TYPE})
    • @Retention(RetentionPolicy.RUNTIME)
    • @Inherited
    • @Documented

    @Target:用于声明注解的作用域,可以是

    • ElementType.CONSTRUCTOR 可作用于构造方法
    • ElementType.FIELD 字段声明
    • ElementType.LOCAL_VARIABLE 局部变量声明
    • ElementType.METHOD 方法声明
    • ElementType.PACKAGE 包声明
    • ElementType.PARAMETER 参数声明
    • ElementType.TYPE 类、接口、枚举、注解声明。

    @Retention:用于声明注解何时保留,也有人们称为生命周期,可以是

    • RetentionPolicy.SOURCE 只在源码显示,编译时会丢弃
    • RetentionPolicy.CLASS 编译时会记录到class中,运行时忽略
    • RetentionPolicy.RUNTIME 运行时存在,可以通过反射读取。

    @Inherited:允许子类继承,即声明该注解会被使用了该注解的类的子类所继承。

    @Documented:使用了这个注解,那么在生成 Javadoc 的时候会包含注解信息。

    所以,使用这4个元注解,就是用来修饰我们自定义的注解的,规定我们自定义的注解在哪些地方能用,什么时候会保留注解信息等等。

    在 JDK 8 新加了一个元注解 @Repeatable,这个也后续再来填坑啦。

  • JDK 自带的注解
  • 常见第三方注解(Spring、MyBatis等等)
  • 自定义注解

如何自定义注解

使用 @interface 关键字进行注解的自定义,写出你自己定义的注解~

public @interface 注解名 { 
    成员变量...以无参数无异常的方式来声明 可以用defalt关键字来指定默认值 } 

举个例子:

讯享网@Target({ 
   ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface TextMessage { 
    // 使用 @interface 关键字定义注解,注解名为 TextMessage String to(); // 成员变量以无参数无异常的方式来声明 String from() default "Jayson Tatum"; // 可以用defalt关键字来指定默认值 String body(); } 

注意事项:

  • 注解只有成员变量,没有方法,虽然这个变量有小括号,看起来挺像方法的。
  • 注解中定义成员变量时它的类型只能是 8 种基本数据类型外加 String 、类、接口、注解、枚举及它们的数组。
  • 若注解只有一个成员变量,则该成员名必须取名为 value() ,然后使用注解的时候参数直接写。
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface A { 
    String value(); } public class B { 
    @A("god23bin") public int var; } 
  • 注解可以没有成员变量,这样的注解称为标识注解。

获取注解信息

还记得开头我说过的话吗?

注解实际上并不是程序本身,只是向编译器提供相关的程序的附加信息,也就是说注解并不会影响程序的运行。

是的,注解这样子,根本不能帮我们做什么事情,顶多是给我们人看的,我们知道它描述什么,此时它就和注释的功能很像了,但是!我们知道,注解不仅仅是给我们人看的,也是给机器看的啊,那么机器是如何看的呢?这就涉及到反射了,需要同个反射去获取注解的信息,进而进行下一步的操作,实现我们想要的功能!

举个例子:

我这里自定义了一个注解 B,作用域为类、接口、枚举、注解、方法、字段(成员变量)。

注解的保留时期(Retention)是运行时(RetentionPolicy.RUNTIME),这是必须的,毕竟反射是运行时动态获取的,不选这个,就不能使用反射获取注解信息了。

该注解有两个成员变量,或者说两个属性,即 idtextid 默认 -1

讯享网@Target({ 
   ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface B { 
    int id() default "-1"; String text(); } 

现在,我们把这个注解声明到 Test 类上:

@B() public class Test { 
    } 

那如何使用反射获取呢?我们可以通过反射中的 Class 对象isAnnotationPresent() 方法判断该类是否使用了某个注解。

讯享网 / * {@inheritDoc} * @throws NullPointerException {@inheritDoc} * @since 1.5 */ @Override public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) { 
    return GenericDeclaration.super.isAnnotationPresent(annotationClass); } 

然后通过它的 getAnnotation() 方法来获取 Annotation 对象

 / * @throws NullPointerException {@inheritDoc} * @since 1.5 */ @SuppressWarnings("unchecked") public <A extends Annotation> A getAnnotation(Class<A> annotationClass) { 
    Objects.requireNonNull(annotationClass); return (A) annotationData().annotations.get(annotationClass); } 

或者是 getAnnotations() 方法来获取 Annotation 数组对象

讯享网 / * @since 1.5 */ public Annotation[] getAnnotations() { 
    return AnnotationParser.toArray(annotationData().annotations); } 

所以,我们可以这样获取注解信息:

@B("god23bin") public class Test { 
    public static void main(String[] args) { 
    boolean isB = Test.class.isAnnotationPresent(B.class); if (isB) { 
    B b = Test.class.getAnnotation(B.class); System.out.println("id:" + b.id()); System.out.println("text:" + b.text()); } } } 

输出结果:

讯享网id:-1 text:god23bin 

同理,位于接口、属性(字段)、方法上的注解,同样可以通过反射获取。

@B(text="I got you today") public class Test { 
    @B(id=1, text="hello") public int var; @B(text="world") public void empty() { 
    } public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException { 
    boolean isB = Test.class.isAnnotationPresent(B.class); if (isB) { 
    // 获取类上的注解 B b1 = Test.class.getAnnotation(B.class); System.out.println("1-id:" + b1.id()); System.out.println("1-text:" + b1.text()); } Field field = Test.class.getDeclaredField("var"); field.setAccessible(true); // 获取字段上的注解 B b2 = field.getAnnotation(B.class); if (b2 != null) { 
    System.out.println("2-id:" + b2.id()); System.out.println("2-text:" + b2.text()); } // 获取方法上的注解 Method method = Test.class.getDeclaredMethod("empty"); B b3 = method.getAnnotation(B.class); if (b3 != null) { 
    System.out.println("3-id:" + b3.id()); System.out.println("3-text:" + b3.text()); } } } 

输出结果:

讯享网1-id:-1 1-text:I got you today 2-id:1 2-text:hello 3-id:-1 3-text:world 

好了,到这里,上面已经知道我们可以通过反射获取注解信息,但是目前仅仅只是获取信息而已,如何实现其他功能呢?这个问题问的好,后续再来填坑吧,不过还是一样,离不开反射。

以上就是注解的基本内容了。

最后的最后

由本人水平所限,难免有错误以及不足之处, 屏幕前的靓仔靓女们 如有发现,恳请指出!

最后,谢谢你看到这里,谢谢你认真对待我的努力,希望这篇博客对你有所帮助!

你轻轻地点了个赞,那将在我的心里世界增添一颗明亮而耀眼的星!

小讯
上一篇 2025-04-04 18:45
下一篇 2025-03-26 13:34

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/23784.html