1、注解概述

从JDK5.0开始,Java增加对元数据(MetaData)的支持,也就是注解(Annotation)。其实我们早就已经接触过注解了,例如我们经常在Java代码中可以看到 “@Override”,“@Test”等等这样的东西,它们就是Java中的注解。注解可以像修饰符一样使用,可以用于修饰包、类、构造器、方法、成员变量、参数、局部变量的声明。

我们需要注意的是,注解与注释是有一定区别的,注解就是代码里面的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过注解开发人员可以在不改变原有代码和逻辑的情况下在源代码中嵌入补充信息。而注释则是用以说明某段代码的作用,或者说明某个类的用途、某个方法的功能和介绍,以及该方法的参数和返回值的数据类型及意义等等。

2、Java内置注解

在JavaSE部分,注解的使用往往比较简单,Java中提供了5个内置注解,它们分别是:

①、@Override:标注该方法是重写父类中的方法。

这个注解一个是我们见得最多的一个了,提示这个方法是重写于父类的方法。

②、@Deprecated:标记某个功能已经过时,用于定义过时的类、方法、成员变量等。

这个注解想必大家应该都有碰到过,在使用Date日期类的时候,里面有大量过时的方法,我们来定义一个Date类来调用一个方法。

这个getDay()方法就是过时的,我们点击进去看一下这个方法的源码:

果然这个方法是用@Deprecated修饰过的。同时也可以发现我们在调用过时元素时,编译器在编译阶段遇到这个注解时会发出提醒警告,告诉开发者正在调用一个过时的元素,当然如果不想看到警告我们可以抑制它的出现。

③、@SuppressWarnings:抑制编译器警告。

上面说到用@Deprecated修饰过的元素在调用时会有警告,我们可以用@SuppressWarnings注解来抑制警告的出现。

可以发现左边的警告没有了。@SuppressWarnings这个注解中参数非常的多,这里介绍几个常见的参数:

  • all:抑制所有警告。
  • deprecation:抑制过期方法警告。
  • null:忽略对null的操作。
  • unchecked:抑制没有进行类型检查操作的警告。
  • unused:抑制没被使用过的代码的警告。

如果需要了解更多的可以去查看官方文档。

④、@FunctionaInterface:指定接口必须为函数式接口。

这个注解是Java8出现的新特性。这个函数式接口的意思就是接口中有一个且仅有一个抽象方法,但是可以有多个非抽象方法,如果不定义或定义多个抽象方法就会报错。

正式因为JDK 8中lambda表达式的引入,使得函数式接口在Java中变得越来越流行。因为这些特殊类型的接口可以用lambda表达式、方法引用或构造函数引用轻松替换。

⑤、@SafeVarargs:抑制"堆污染警告"。

这个注解是在Java7中引入,主要目的是处理可变长参数中的泛型,此注解告诉编译器:在可变长参数中的泛型是类型安全的。可变长参数是使用数组存储的,而数组和泛型不能很好的混合使用。因为数组元素的数据类型在编译和运行时都是确定的,而泛型的数据类型只有在运行时才能确定下来,因此当把一个泛型存储到数组中时,编译器在编译阶段无法检查数据类型是否匹配,因此会给出警告信息。

我们来看下面这个示例:

public class Test {@SafeVarargs//这里告诉编译器类型安全,不让有警告。其实方法体内容类型不安全public static void show(List<String>...lists){Object[] arry=lists;List<Integer> intList=Arrays.asList(11,22,33);arry[0]=intList;//这里就是堆污染,这里没有警告,是因为只针对于可变长参数泛型String str=lists[0].get(0);//java.lang.ClassCastException
    }public static void main(String[] args) {List<String> list1=Arrays.asList("AA","BB","CC");List<String> list2=Arrays.asList("DD","EE","DD");show(list1,list2);}
}

通过上述的示例,我们将intList赋给array[0],array[0]的类型是List<String>,但是储引用到实际为List<Integer>类型的值,这个无效的引用被称为堆污染。由于直到运行时才能确定此错误,因此它会在编译时显示为警告,这里没有警告,是因为只针对于可变长参数泛型,并在运行时出现ClassCastException。

注意:@SafeVarargs注解只能用在参数长度可变的方法或构造方法上,且方法必须声明为static或final,否则会出现编译错误。

3、自定义注解

我们在享受注解给我们带来方便地同时,我们自己应该要知道怎么去定义注解。注解的自定义非常的简单,通过 @interface关键字进行定义,可以发现这个关键字和接口interface很相似,就在前面加了一个 @符号,但是它和接口没有任何关系。自定义注解还需要注意的一点是:所有的自定义注解都自动继承了java.lang.annotation.Annotation这个接口。自定义注解的格式:

public @interface 注解名 {//属性
}    

同样我们可以在注解中定义属性,它的定义有点类似于方法,但又不是方法,在注解中是不能声明普通方法的。注解的属性在注解定义中以无参数方法的形式来声明,其方法名定义了属性的名字,其返回值定义了该属性的类型,我们称为配置参数。它们的类型只能是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型以上所有类型的数组。例如:

//定义了一个MyAnnotation注解
public @interface MyAnnotation {String[] value();
}@MyAnnotation(value = "hello")
class Test{}

上面注解代码中,定义了一个String的value数组。然后我们在使用的时候,就可以使用 属性名称=“xxx” 的形式赋值。

注解中属性还可以有默认值,默认值需要用 default 关键值指定。比如:.

//定义了一个MyAnnotation注解
public @interface MyAnnotation {String id();String[] value() default {"AA","BB"};
}@MyAnnotation(id="one")
class Test{}

上面定义了 id 属性没有默认值,而value属性中则设置了默认值,所以在使用注解的时候只需给 id 属性赋值即可,value可以不用写。

通过以上形式自定义的注解暂时都还没有任何实用的价值,因为自定义注解必须配上注解的信息处理流程(使用反射)才有意义。如何让注解真真的发挥作用,主要就在于注解处理方法,所以接下来我们将学习元注解和注解的反射。

4、元注解

元注解就是用来修饰其他注解的注解。我们随便点进一个注解的源码都可以发现有元注解。

Java5.0中定义了4个标准的元注解类型,它们被用来提供对其它注解类型作说明:

  • @Retention
  • @Target
  • @Documented
  • @Inherited

而Java8.0中又增加了一个新的元注解类型:

  • @Repeatable

所以接下来我们将逐个分析它们的作用和使用方法。

1、@Retention:用于指定该Annotation的生命周期。

这个元注解只能用于修饰一个Annotation定义,它的内部包含了一个RetentionPolicy枚举类型的属性,而这个枚举类中定义了三个枚举实例,SOURCE、CLASS、RUNTIME。它们各个值的意思如下:

  • RetentionPolicy.SOURCE:在源文件中有效(即源文件保留),在编译器进行编译时它将被丢弃忽视。
  • RetentionPolicy.CLASS:在class文件中有效(即class保留),当Java程序运行时,它并不会被加载到 JVM 中,只保留在class文件中。这个是默认值。
  • RetentionPolicy.RUNTIME:在运行时有效(即运行时保留),当Java程序运行时,注解会被加载进入到 JVM 中,所以我们可以使用反射获取到它们。

比较典型的是@SuppressWarnings注解,如果我们用 javap -c去反编译它是看到这个注解的,因为在编译的时候就已经被丢弃了。

②、@Target:用于指定该Annotation能够用在哪些地方。

@Target内部定义了一个枚举类型的数组ElementType[] value(),在ElementType这个枚举类中参数有很多,我们来看一下:

  • TYPE:用于描述类、接口(包括注解类型) 或enum声明
  • FIELD:用于描述域即类成员变量
  • METHOD:用于描述方法
  • PARAMETER:用于描述参数
  • CONSTRUCTOR:用于描述构造器
  • LOCAL_VARIABLE:用于描述局部变量
  • ANNOTATION_TYPE:由于描述注解类型
  • PACKAGE:用于描述包
  • TYPE_PARAMETER:1.8版本开始,描述类、接口或enum参数的声明
  • TYPE_USE:1.8版本开始,描述一种类、接口或enum的使用声明

③、@Document:表示Annotation可以被包含到javadoc中去。默认情况下javadoc是不包含注解的。

由于这个比较简单所以不细说。

④、@Inherited:被它修饰的Annotation将具有继承性。

@Inherited修饰过的Annotation其子类会自动具有该注解。在实际应用中,使用情况非常少。

⑤、@Repeatable:用于指示它修饰的注解类型是可重复的。

这个注解是在Java8中新出的特性,说到这个可重复注解可能有点不理解。我们通过示例来理解一下:

先定义一个MyAnnotation注解:

@Inherited
@Documented
@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD,ElementType.PARAMETER})
public @interface MyAnnotation {String value() default "Hello";
}

这里需要说明@Repeatable(MyAnnotations.class),它表示在同一个类中@MyAnnotation注解是可以重复使用的,重复的注解被存放至@MyAnnotations注解中。

然后再定义一个MyAnnotations注解:

@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD,ElementType.PARAMETER})
public @interface MyAnnotations {MyAnnotation[] value();
}

这个MyAnnotations注解里面的属性必须要声明为要重复使用注解的类型数组MyAnnotation[] value();,而且相应的生命周期和使用地方都需要同步。否则就会编译报错!

进行测试:

@MyAnnotation(value = "World")
@MyAnnotation(value = "World")
public class Test{}

而在Java8之前没有@Repeatable注解是这样写的:

@MyAnnotations({@MyAnnotation(value = "World"),@MyAnnotation(value = "World")})
public class Test{}

5、注解处理器(使用反射)

以上讲的所以注解的定义都只是一个形式,实际上还并没有什么可使用的价值,而注解的核心就是使用反射来获取到它们,所以下面我们要来学习注解处理器(使用反射)的使用。

下面参考:https://www.cnblogs.com/peida/archive/2013/04/26/3038503.html

java.lang.reflect 包下主要包含一些实现反射功能的工具类,实际上,java.lang.reflect 包所有提供的反射API扩充了读取运行时Annotation信息的能力。当一个Annotation类型被定义为RUNTIME的注解后,该注解才能是运行时可见,当class文件被装载时被保存在class文件中的Annotation才会被虚拟机读取。
AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的如下四个个方法来访问Annotation信息:

  • 方法1:<T extends Annotation> T getAnnotation(Class<T> annotationClass): 返回改程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。
  • 方法2:Annotation[] getAnnotations():返回该程序元素上存在的所有注解。
  • 方法3:boolean is AnnotationPresent(Class<?extends Annotation> annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false.
  • 方法4:Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。

一个简单的注解处理器:  

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)//这里必须定义为RUNTIME
public @interface MyAnnotation {String id();String[] value() default {"AA","BB"};
}@MyAnnotation(id = "hello")
class Test{public static void main(String[] args) {boolean annotationPresent = Test.class.isAnnotationPresent(MyAnnotation.class);System.out.println(annotationPresent);if ( annotationPresent ) {MyAnnotation myAnnotation = Test.class.getAnnotation(MyAnnotation.class);System.out.println("id:"+myAnnotation.id());System.out.println("value:"+ Arrays.toString(myAnnotation.value()));}}
}

程序运行结果:

上面的例子只是作用在类上面的注解,如果要作用在属性、方法等上面的注解我们应该怎么获取呢?

定义作用于类上面的MyAnnotation注解:

//作用于类上面的注解
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)//这里必须定义为RUNTIME
public @interface MyAnnotation {String[] value() default "";
}

定义作用于属性上面的AttributeAnnotation注解:

//作用于属性上的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)//这里必须定义为RUNTIME
public @interface AttributeAnnotation {String value();
}

定义作用于方法上面的MethodAnnotation注解:

//作用于方法上的注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)//这里必须定义为RUNTIME
public @interface MethodAnnotation {String value();
}

然后就是测试了:

@MyAnnotation(value = "MyAnnotation")
class Test{@AttributeAnnotation(value = "AttributeAnnotation")String str;@MethodAnnotation(value = "MethodAnnotation_show")public void show(){System.out.println("MethodAnnotation_show");}@MethodAnnotation(value = "MethodAnnotation_display")public void display(){System.out.println("MethodAnnotation_display");}public static void main(String[] args) {//获取类上面的注解boolean annotationPresent = Test.class.isAnnotationPresent(MyAnnotation.class);System.out.println(annotationPresent);if ( annotationPresent ) {MyAnnotation myAnnotation = Test.class.getAnnotation(MyAnnotation.class);System.out.println("class-annotation:"+Arrays.toString(myAnnotation.value()));}try {//获取单个属性中的注解Field str = Test.class.getDeclaredField("str");AttributeAnnotation attributeAnnotation = str.getAnnotation(AttributeAnnotation.class);if (attributeAnnotation!=null){System.out.println("attribute-annotation:"+attributeAnnotation.value());}//获取多个方法中的注解Method[] declaredMethods = Test.class.getDeclaredMethods();for (Method declaredMethod : declaredMethods) {MethodAnnotation methodAnnotation = declaredMethod.getAnnotation(MethodAnnotation.class);if (methodAnnotation!=null){System.out.println("method-annotation:"+methodAnnotation.value());}}} catch (NoSuchFieldException e) {e.printStackTrace();}}
}

程序运行结果:

小弟菜鸟只能领悟这么多了,如果有错误或者需要补充的地方欢迎大家留言指出。谢谢!!!

转载于:https://www.cnblogs.com/tang-hao-/p/11340632.html

夯实Java基础(十七)——注解(Annotation)相关推荐

  1. 夯实Java基础系列15:Java注解简介和最佳实践

    Java注解简介 注解如同标签 Java 注解概述 什么是注解? 注解的用处 注解的原理 元注解 JDK里的注解 注解处理器实战 不同类型的注解 类注解 方法注解 参数注解 变量注解 Java注解相关 ...

  2. 夯实 Java 基础 - 注解

    夯实 Java 基础 - 注解 不知道大家有没有一种感觉,当你想要了解某个知识点的时候,就会发现好多技术类 APP 或者公众号在推一些关于这个知识点的文章.也许这就是大数据的作用,这也说明总有人比你抢 ...

  3. 夯实Java基础系列9:深入理解Class类和Object类

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...

  4. string substring的用法_夯实Java基础系列3:一文搞懂String常见面试题,从基础到实战...

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...

  5. java中this_夯实Java基础系列7:一文读懂Java 代码块和执行顺序

    目录 #java中的构造方法 #构造方法简介 #构造方法实例 #例-1 #例-2 #java中的几种构造方法详解 #普通构造方法 #默认构造方法 #重载构造方法 #java子类构造方法调用父类构造方法 ...

  6. [转载] 夯实Java基础系列8:深入理解Java内部类及其实现原理

    参考链接: Java内部类 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tut ...

  7. Java中的注解(Annotation)处理器解析

    Java中的注解(Annotation)是一个很神奇的东西,特别现在有很多Android库都是使用注解的方式来实现的.一直想详细了解一下其中的原理.很有幸阅读到一篇详细解释编写注解处理器的文章.本文的 ...

  8. 夯实Java基础系列22:一文读懂Java序列化和反序列化

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...

  9. 夯实JAVA基础(个人笔记)

    夯实JAVA基础(个人笔记) 一.Java 异常处理 二.泛型 三.集合 四.算法 五.输入与输出(字节流和字符流) 六.并发和多线程 七.网络连接 八.lambda表达式 九.反射 十.Java S ...

  10. 夯实Java基础系列11:深入理解Java中的回调机制

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...

最新文章

  1. Rust 1.30带来更多元编程支持,并改进了模块系统
  2. 围观窗体与组件03 - 零基础入门学习Delphi25
  3. 多图 | 600岁“网红”的10亿+营收变现(结尾有彩蛋)
  4. 敏捷 橄榄球运动_为什么我为大学橄榄球博客选择Wordpress
  5. Jquery图片放大镜效果
  6. LINUX SHELL安装deb命令
  7. Ubuntu22 超nice的QQ(Icalingua++)安装步骤,拒绝走弯路!!
  8. VMWARE平台STS证书过期
  9. moment.js计算时间差
  10. PS长图快速切片_【干货】Photoshop(PS)零基础教学(界面篇)
  11. mysql创建用户并授权语句_MySQL创建用户与授权
  12. 名帖59 褚遂良 小楷《千字文》
  13. java三元运算符用的多不多_Java多个三元运算符
  14. 文件备份 服务器,服务器文件备份
  15. 鲲鹏arm服务器编译安装PaddlePaddle
  16. android 8 忘记图案,安卓手机忘记锁屏图案密码六种解决办法
  17. 一个嵌入式牛人学习经历
  18. 玩的第一个黑客小游戏,isa test
  19. 数据结构—— 一元多项式的加法运算
  20. 2016年美国数学奥林匹克竞赛试题

热门文章

  1. ad19做直插封装 ipc_AD19的IPC封装向导使用
  2. 35岁的程序员如果不转行,从事哪些细分行业比较好?
  3. 苹果手机没充满电就拔下,会对电池造成伤害吗?
  4. 股票预埋单第二天还有效么?
  5. 如果有200万存款吃利息,可以不用上班吗?
  6. 戴机械手表有哪些事情就不能做了?
  7. 荣耀play4t pro、荣耀x10、华为nova6,哪一个更值得买?
  8. 一定要吃透的四个人性真相
  9. 老板要先想明白三件事
  10. Solidity 中 revert(), assert() 和 require() 的使用方法