夯实Java基础(十七)——注解(Annotation)
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();
- }
- }
- }
程序运行结果:
小弟菜鸟只能领悟这么多了,如果有错误或者需要补充的地方欢迎大家留言指出。谢谢!!!
夯实Java基础(十七)——注解(Annotation)的更多相关文章
- java基础篇---注解(Annotation)
一.概念 Annontation是Java5开始引入的新特征.中文名称一般叫注解.它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类.方法.成员变量等)进行关 ...
- Java 基础之--注解Annotation详解
自定义注解入门: public @interface Annotation01 { //set default value ""; String value() default & ...
- 夯实Java基础系列15:Java注解简介和最佳实践
Java注解简介 注解如同标签 Java 注解概述 什么是注解? 注解的用处 注解的原理 元注解 JDK里的注解 注解处理器实战 不同类型的注解 类注解 方法注解 参数注解 变量注解 Java注解相关 ...
- 夯实Java基础系列目录
自进入大学以来,学习的编程语言从最初的C语言.C++,到后来的Java,. NET.而在学习编程语言的同时也逐渐决定了以后自己要学习的是哪一门语言(Java).到现在为止,学习Java语言也有很长一段 ...
- 夯实Java基础系列9:深入理解Class类和Object类
目录 Java中Class类及用法 Class类原理 如何获得一个Class类对象 使用Class类的对象来生成目标类的实例 Object类 类构造器public Object(); register ...
- Java基础之理解Annotation(与@有关,即是注释)
Java基础之理解Annotation 一.概念 Annontation是Java5开始引入的新特征.中文名称一般叫注解.它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata) ...
- Java 基础之认识 Annotation
Java 基础之认识 Annotation 从 JDK 1.5 版本开始,Java 语言提供了通用的 Annotation 功能,允许开发者定义和使用自己的 Annotation 类型.Annotat ...
- 夯实Java基础系列5:Java文件和Java包结构
目录 Java中的包概念 包的作用 package 的目录结构 设置 CLASSPATH 系统变量 常用jar包 java软件包的类型 dt.jar rt.jar *.java文件的奥秘 *.Java ...
- 夯实Java基础系列13:深入理解Java中的泛型
目录 泛型概述 一个栗子 特性 泛型的使用方式 泛型类 泛型接口 泛型通配符 泛型方法 泛型方法的基本用法 类中的泛型方法 泛型方法与可变参数 静态方法与泛型 泛型方法总结 泛型上下边界 泛型常见面试 ...
- Java工程师学习指南第1部分:夯实Java基础系列
点击关注上方"Java技术江湖",设为"置顶或星标",第一时间送达技术干货. 本文整理了微信公众号[Java技术江湖]发表和转载过的Java优质文章,想看到更多 ...
随机推荐
- set.contains()分析
先看一段代码 Set s = new HashSet(); List<String> list = new ArrayList<>(); list.add("a&qu ...
- C语言指针专题——指针怎么就很灵活?
最近在研读 C Primer pkus(第五版)中文版,老外写的还是很经典的,推荐给朋友们,购买地址:C primer plus 5版中文版购买 另外再推荐两本书: 1. 2017年9月全国计算机二级 ...
- 用Python玩数据-笔记整理-第二章-练习与测试
课间练习: 经典问题的Python编程 按公式:C= 5/9×(F-32) ,将华氏温度转换成摄氏温度,并产生一张华氏0-300度与对应的摄氏温度之间的对照表(每隔20度输出一次) 验证命题:如果一 ...
- nu.xom:Attribute
Attribute: 机翻 Attribute copy():生成一份当前Attribute的拷贝,但是它没有依附任何Element Node getChild(int position) :因为At ...
- 在eclipse中利用正则表达式查找替换
众所周知,eclipse是可以用正则表达式来进行查找的,那么怎么利用正则表达式进行替换呢? 方法也很简单,就是在Replace with: 里面输入$来代表捕获型括号的匹配结果,$1为第一个匹配结果, ...
- Button事件的三种实现方法
onclick事件的定义方法,分为三种,分别为在xml中进行指定方法:在Actitivy中new出一个OnClickListenner():实现OnClickListener接口三种方式. 1.在xm ...
- Windows下ElasticSearch的Head安装及基本使用
前段时间,有一朋友咨询我,说es的head插件一直安装失败,为了给朋友解惑,自己百度博文并实践了一番,也的确踩了些坑,但我给爬了起来.今天就来分享下实践心得并跳过的坑. ElasticSearch 是 ...
- [POJ 2888]Magic Bracelet[Polya Burnside 置换 矩阵]
也许更好的阅读体验 \(\mathcal{Description}\) 大意:给一条长度为\(n\)的项链,有\(m\)种颜色,另有\(k\)条限制,每条限制为不允许\(x,y\)颜色连在一起.要求有 ...
- VUE过滤器的使用 vue 时间格式化
过滤器介绍 官方教程地址:https://cn.vuejs.org/v2/guide/filters.html 过滤器常被用于一些文本格式化 我们可以自定义过滤器,可以实现各种各样的功能. vue时间 ...
- sessionID是如何在客户端和服务器端传递的?
sessionID是如何在客户端和服务器端传递的? 服务器初次创建session的时候后返回session到客服端(在返回头(response)中有setCookie),浏览器会把sessionnam ...