本文转载自掘金,作者-jack_xu。
主页:https://juejin.cn/user/1802854801877191
认识注解
注解( Annotation )相当于一种标记,在程序中加入注解就等于为程序打上某种标记,没有加,则等于没有任何标记,以后,javac编译器、开发工具和其他程序可以通过反射来了解你的类及各种元素上有无何种标记,看你的程序有什么标记,就去干相应的事,标记可以加在包、类,属性、方法,方法的参数以及局部变量上。
作用分类:
1、编写文档:通过代码里标识的注解生成文档【生成文档doc文档】
2、代码分析:通过代码里标识的注解对代码进行分析【使用反射】
3、编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【Override】
JDK中预定义的注解
JDK1.5之后内部提供的三个注解:
- @Deprecated 意思是“废弃的,过时的”
- @Override 意思是“重写、覆盖”
- @SuppressWarnings 意思是“压缩警告”,作用:用于抑制编译器产生警告信息。
接下来我们依次介绍一下。
@Deprecated
该注解标注的内容,表示已过时,我们先定义两个方法,show1()方法因为过时了,功能跟不上需求了,所以我们在它上面打了一个@Deprecated注解,show2()是一个更强大的功能。
@Deprecated
public void show1(){
// 发现过时了,功能跟不上需求了
}
public void show2(){
// 功能更加强大的方法
}
我们在调用的地方发现有一条红色的横线,意思就是说这个方法已经废弃了,不建议使用了。那既然过时了你直接把这个方法删了不就得了,又要留着又要告诉你不要使用,岂不是多此一举?其实这么做的原因就是为了做到向下兼容,你把它删了,那之前老代码引用这个方法的地方都得报错,所以我们需要通过这种方法才做到平滑过渡。
还有JDK中自带的 Data 类,getYear() 方法上也有根红色的横线,这个也是因为打了@Deprecated 的注解,我们点进去看下确实如此。这样的例子还有很多,小伙们在平时的开发中注意观察下,过期的方法就不要使用啦...
@Override
检测被该注解标注的方法是否是继承自父类(接口)的,这个也比较简单,它可以避免方法名和参数写错,起到检查的作用。
@Override
public String toString() {
return "xxxx";
}
复制代码
@SuppressWarnings
压制警告,我们看下图 str 没有用到,编辑器提示了一个粉红色的波浪线,并且告诉我们 variable 'str' is never used
这只是其中一个例子,还有各种各样的警告,这时候我们会觉得很烦,怎么办呢,我们就可以用 @SuppressWarnings,告诉编辑器不要给我提示了,一般我们传递参数all,@SuppressWarnings("all"),此时波浪线就没有了。
当然如果不想抑制所有的话,有些警告我们想看到的话,也可以抑制具体的值,如下表所示:
参数 |
说明 |
说明 |
all |
to suppress all warnings |
抑制所有警告 |
boxing |
to suppress warnings relative to boxing/unboxing operations |
抑制装箱、拆箱操作时候的警告 |
cast |
to suppress warnings relative to cast operations |
抑制映射相关的警告 |
dep-ann |
to suppress warnings relative to deprecated annotation |
抑制启用注释的警告 |
deprecation |
to suppress warnings relative to deprecation |
抑制过期方法警告 |
fallthrough |
to suppress warnings relative to missing breaks in switch statements |
抑制确在switch中缺失breaks的警告 |
finally |
to suppress warnings relative to finally block that don’t return |
抑制finally模块没有返回的警告 |
hiding |
to suppress warnings relative to locals that hide variable |
抑制与隐藏变量的局部变量相关的警告 |
incomplete-switch |
to suppress warnings relative to missing entries in a switch statement |
忽略没有完整的switch语句 |
nls |
to suppress warnings relative to non-nls string literals |
忽略非nls格式的字符 |
null |
to suppress warnings relative to null analysis |
忽略对null的操作 |
rawtypes |
to suppress warnings relative to un-specific types when using generics on class params |
使用generics时忽略没有指定相应的类型 |
restriction |
to suppress warnings relative to usage of discouraged or forbidden references |
抑制与不鼓励或禁止引用的使用有关的警告 |
serial |
to suppress warnings relative to missing serialVersionUID field for a serializable class |
忽略在serializable类中没有声明serialVersionUID变量 |
static-access |
to suppress warnings relative to incorrect static access |
抑制不正确的静态访问方式警告 |
synthetic-access |
to suppress warnings relative to unoptimized access from inner classes |
抑制子类没有按最优方法访问内部类的警告 |
unchecked |
to suppress warnings relative to unchecked operations |
抑制没有进行类型检查操作的警告 |
unqualified-field-access |
to suppress warnings relative to field access unqualified |
抑制没有权限访问的域的警告 |
unused |
to suppress warnings relative to unused code |
抑制没被使用过的代码的警告 |
自定义注解
除了JDK自带的注解,我们还可以编写自定义注解来满足我们平时开发中的一些需要,这个也是本篇文章的重点, 创建注解的格式如下:
// 元注解
public @interface 注解名称{
// 属性列表
}
复制代码
自定义的注解反编译后的内容,注解的本质其实就是一个接口,继承Annotation父接口
public interface MyAnno extends java.lang.annotation.Annotation {
}
复制代码
下面写了一个注解的例子,我们来依次介绍下。。
元注解
在注解类上使用另一个注解类,那么被使用的注解类就称为元注解,JDK中给我们提供的4个元注解
@Retention
@Retention 注解决定 MyAnnotation 注解的生命周期,描述注解被保留到的阶段,SOURCE < CLASS < RUNTIME
- SOURCE:让 MyAnnotation 注解只在java源文件中存在,编译成 .class文件后注解就不存在了
- CLASS:让 MyAnnotation 注解在java源文件(.java文件)中存在,编译成 .class文件后注解也还存在,被MyAnnotation 注解类标识的类被类加载器加载到内存中后 MyAnnotation 注解就不存在了
- RUNTIME:让 MyAnnotation 这个注解的生命周期一直到程序运行时都存在
@Target
@Target 元注解决定了一个注解可以标识到哪些成分上,如标识在在类身上,或者属性身上,或者方法身上等成分
- ElementType.TYPE:可以作用在类上
- ElementType.METHOD:可以作用在方法上
- ElementType.FIELD:可以作用在成员变量上
@Documented
描述注解是否被抽取到JavaDoc api中
@inherited
描述注解是否可以被子类继承
属性
上文中说过注解的本质就是接口,所以属性就是在接口中定义的抽象方法。
语法:类型 属性名();返回的结果必须是如下类型:
- 基本数据类型
- String类型
- 枚举类型
- 注解
- 以上类型的数组
上图中自定义了一个color的属性,在使用中如下:
//应用MyAnnotation注解的color属性
@MyAnnotation(color = "red")
public class AnnotationTest {
public static void main(String[] args) {
//用反射方式获得注解对应的实例对象后,在通过该对象调用属性对应的方法
MyAnnotation annotation = (MyAnnotation) AnnotationTest.class.getAnnotation(MyAnnotation.class);
//输出red
System.out.println(annotation.color());
}
}
复制代码
属性赋值一些注意点
1、如果定义的属性时,使用 default 关键字给属性默认初始值,可以在使用注解时不赋值
// 指定默认值 在使用注解的时候没有给该属性赋值,那么就使用默认值
String color() default "blue";
复制代码
2、如果一个注解中有一个名称为value的属性,且你只想设置value属性(即其他属性都采用默认值或者你只有一个value属性),那么可以省略掉value=部分。
@SuppressWarnings("deprecation")
@Retention(RetentionPolicy.RUNTIME)
@Target( { ElementType.METHOD, ElementType.TYPE })
public @interface MyAnnotation {
String color() default "blue";//为属性指定缺省值
String value();//定义一个名称为value的属性
}
@MyAnnotation("xxx")//使用的时候等价于@MyAnnotation(value="xxx")
复制代码
3、数组赋值的时候,值使用{}包裹,如果数组中只有一个值,那么{}可以省略
//数组类型的属性
int[] arrayAttr() default {1,2,4};
//使用的时候加上{}
@MyAnnotation(arrayAttr={2,4,5})
//如果数组属性只有一个值,这时候属性值部分可以省略大括号
@MyAnnotation(arrayAttr=2),
复制代码
4、枚举
//枚举类型的属性
EumTrafficLamp lamp() default EumTrafficLamp.RED;
//使用注解
@MyAnnotation(lamp=EumTrafficLamp.GREEN)
复制代码
5、注解的属性值又是一个注解
MetaAnnotation annotationAttr() default @MetaAnnotation("aaa");
复制代码
自定义注解的案例
下面我们通过注解的方式来实现一个通过反射创建一个对象的功能。
自定义注解:
/**
* 自定义注解
* 该注解表面要执行哪个类中的哪个方法
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface InvokAnno {
String className();
String methodName();
}
复制代码
定义一个实体类:
public class Person {
public void show() {
System.out.println("person show ....");
}
}
复制代码
通过反射创建对象:
@InvokAnno(className = "com.jackxu.annotation.Person", methodName = "show")
public class InvokeTest {
public static void main(String[] args) throws Exception {
// 获取类对象
Class<InvokeTest> clazz = InvokeTest.class;
// 获取类对象中的注解
InvokAnno an = clazz.getAnnotation(InvokAnno.class);
/**
* 注解的本质是接口,获取到的an其实是接口的实现,即
* public class MyInvokAnno implements InvokAnno{
*
* String className(){
* return "com.jackxu.annotation.Person";
* }
* String methodName(){
* return "show";
* }
* }
*/
// 获取注解中对应的属性
String className = an.className();
String methodName = an.methodName();
System.out.println(className);
System.out.println(methodName);
// 通过反射的方式实现接口的功能
Class<?> aClass = Class.forName(className);
Method show = aClass.getDeclaredMethod("show");
// 执行对应的方法
Object o = aClass.newInstance();
show.invoke(o);
}
}
复制代码
执行一下,好,大功告成!
自定义注解一般我们应用在拦截器或者AOP里面,用来设计自己的框架,使代码看起来非常优雅。
注意:本文归作者所有,未经作者允许,不得转载