转载自https://www.zybuluo.com/xiaohaizi/note/965272
上集回顾
上集中唠叨了只能创建有限个对象的枚举类型,用enum
声明,直接填写对象名便可以使用,设计java的大叔们为了我们的方便把能省略的尽量都给省略了,赶紧感谢他们吧~
元数据
这个世界除了真实数据
之外,还有好多针对真实数据
的描述数据
。比如对于一个字符串"你真蠢"
我们可以从不同的角度去描述它:
- 这个字符串有3个字符 (字符数量角度)
- 这个字符串是中文的 (语言角度)
- 这个字符串是骂人的话 (用途角度)
- 这个字符串是王庆峰说的 (说话者角度)
对于真实数据"你真蠢"
来说,上述从不同角度来描述这个真实数据的数据,比如说"字符数量是3"
、"语言是中文"
等这些描述数据,我们也称为元数据
,英文名叫metadata
,为了把这些描述的数据也记下来,我们通常可以定义一个文件,文件里写成这样:
字符数量:3
语言:中文
用途:骂人
说话者:王庆峰
这些描述数据,也就是元数据
是跟真实数据没关系的,每个人都可以从他关心的角度去编写真实数据的描述数据,所以你也可以给"你真蠢"
这个真实数据定义自己关心的一些元数据
,加在自己的描述文件里试试哈~
对java代码的各个部分的描述
除了字符串,世间的所有东西都可以用来描述,来产生对应的描述数据,也就是所谓的元数据
。我们当然也可以描述我们的java代码了。我们知道我们写的java代码是由好多部分组成的,比如:包名
、类名
、字段
、方法
啥的。比如我们定义一个Empolyee
类:
public class Empolyee {
private String name; //姓名
private int age; //年龄
private String id; //身份证号
private float salary; //工资,单位:元
public Person(String name, int age, String id, float salary) {
this.name = name;
this.age = age;
this.id = id;
this.salary = salary;
}
// ... 为节省篇幅,省略字段的getter/setter方法
public void laugh() {
System.out.println("laughing");
}
public void cry() {
System.out.println("crying");
}
public void run() {
System.out.println("running");
}
public void walk() {
System.out.println("standing");
}
}
我们可以对这个代码的各个部分添加描述,把这些描述存储到单独的文件里去,这些描述并不会对代码的运行产生一毛钱的影响。比如我们对laugh
方法进行描述,可以写个这样的描述文件:
是否是面部表情:是
是否锻炼身体:否
累不累:不累
如果我们乐意,我们可以对这个Empolyee
类的各个部分都可以从我们关心的角度添加一些描述数据。但是话说回来,我们添加了一堆描述数据,它们又不会影响程序的运行,我们费这工夫有啥用啊?
哈哈,这些描述文件的存在并不是去影响程序执行过程的,而是为了方便我们对程序结构的分析。这些描述都是从我们自己角度去添加的,你想啊,如果有一天你想知道这个类里有哪些是有关面部表情的方法,你就可以直接查看这些描述文件就好了。所以说,如果我们并不想分析这些描述文件,那它们便没一分钱卵用~
从我自己的角度来说,现在我想给Empolyee
类中的字段设计一个Excel表格,但是我们知道每个Excel单元格都有格式,我想让有些字段有一些特殊格式,所以我给Empolyee
中的各个字段加了一些描述:
`name`字段对应`数值`的单元格格式,居中加粗,14号字体;
`age`字段对应`数值`的单元格格式;
`id`字段对应`文本`的单元格格式;
`salary`字段对应`货币`的单元格格式,加粗;
有了这个描述文件,后边即使不是我亲自去把各个字段转为Excel表格,随便换一个人,只要他能看懂这个描述文件,他就可以针对这个描述文件进行单元格格式设置。
但是单独定义描述文件的话是有不好的地方的,拿这个生成Excel表格的描述文件为例,如果我们之后修改了java的源代码,比如加了个籍贯
的字段,那我们也需要在对应的描述文件中添加一下该字段的描述;或者有时候我们一时迷糊,java源代码中删除掉了一个字段,但是描述文件中该字段还存在。这都是因为我们要维护两个地方的害处,所以设计java的大叔们设计了一种可以直接在java源代码中添加对代码的各个部分进行描述的方式 --- 注解
。
注解
这个注解
的定义使用方式很简单:
先定义一个注解
我们需要用
@interface
的方式去声明注解,比如我们想声明一个单元格格式的注解,这么写就好了:public @interface CellFormat {
}
这样,
CellFormat
就表示一个单元格格式的注解了,但是格式是有具体内容,比如是否是黑体,字体大小,对齐方式啥的,所以需要写成这样:public @interface CellFormat {
String format();
boolean isBold() default false;
int fontSize() default 12;
String align() default "left";
}
就像是在接口中声明方法一样,这些声明的方法也称为
注解元素
,这些元素就代表着单元格格式的具体内容。大家看到,有的方法后边加了default
,意思就是这个方法的默认值是什么,比如isBond
方法的默认值就是false
,fontSize
的默认值就是12,这些默认值有啥用,我们稍后再看。在需要描述的代码上加
@+注解名
。比如我们想描述
Employee
类的各个字段对应的Excel表格格式,我们就可以这么写:public class Employee {
@CellFormat(format = "数值", isBold = true, fontSize = 14, align = "center")
private String name; //姓名
@CellFormat(format = "数值")
private int age; //年龄
@CellFormat(format = "文本")
private String id; //身份证号
@CellFormat(format = "货币", isBold = true)
private float salary; //工资,单位:元
// ... 为节省篇幅,省略其他方法
}
大家看到了,使用注解的方式就是用
@+注解名
的方式加到我们所描述的代码上,然后在后边加上小括号()
,在小括号里填上对应的方法返回的值。如果某个方法没有设置默认值的话,必须声明其返回值,但是如果某个方法有默认值并且没有手动赋值的话,就采用默认值。
当然,我们定义的注解里也可以没有方法,比如我们定义一个表示是面部表情的注解:
public @interface FacialExpression {
}
然后用它去描述方法的时候,就不用在小括号()
里添加各个方法的返回值了:
public class Employee {
// ... 为节省篇幅,省略其他字段和方法
@FacialExpression
public void laugh() {
System.out.println("laughing");
}
@FacialExpression
public void cry() {
System.out.println("crying");
}
}
定义并使用一个注解就是这么简单,我们再强调一遍:注解的使用对程序的运行没有一毛钱关系,它只是对代码的一种描述,和把这些描述写在单独的文件里没有什么区别,也就是说注解就是代码的一种元数据
。
注解本质
我们把我们写的CellFormat.java
文件编译一下,发现出现了一个CellFormat.class
文件,也就是说这个所谓的注解
变成了一个类,如果我们用javap -c CellFormat.class
再查看一下这个类的具体内容的话,我们会得到下边的代码:
public interface CellFormat extends java.lang.annotation.Annotation {
public abstract java.lang.String format();
public abstract boolean isBold();
public abstract int fontSize();
public abstract java.lang.String align();
}
也就是说,其实我们定义了一个叫CellFormat
的接口,它继承自java.lang.annotation
包中的Annotation
接口。
哈哈,那我们用一个普通的接口去试试看看行不:
interface II {}
public class Employee {
// ... 为节省篇幅,省略其他字段和方法
@II //编译错误❌
public void run() {
System.out.println("running");
}
}
哦,发现了编译错误❌,只有用@interface声明的注解才可以用来描述代码。
注意事项
注解
里的方法可不是随便声明的,是有规定的:
必须是
public
的,如果不填访问权限则默认是public
的。它本质就是个接口嘛!
方法返回类型必须是下边的这些类型:
- 所有基本数据类型(
int
、float
、boolean
、byte
、double
、char
、long
、short
) String
类型Class
类型enum
类型Annotation
类型,它是所有注解的祖宗接口- 以上所有类型的数组
为啥会有这个强制规定,我现在还不是很明白,但是它就是规定,你不遵守就让你的代码编译不过,你能咋滴!所以如果我们在定义
CellFormat
注解的时候加了这个方法:public @interface CellFormat {
Employee getEmployee(); //编译错误❌
}
因为
getEmployee
方法把我们的自定义类型Employee
作为返回类型,就编译错误喽~- 所有基本数据类型(
如果注解的方法名是
value
,并且它是唯一的一个需要赋值的注解元素的时候,则可以在使用时在小括号()
中直接填写值。这个特点比较有意思,比如我们定义一个注解:
public @interface ValueAnnotation {
String value(); //方法名叫做value
int otherMethod() default 5;
}
那我们在使用这个注解的时候,可以直接在小括号
()
里指定值:public class Test {
@ValueAnnotation("abc")
public void method();
}
@ValueAnnotation("abc")
的意思和@ValueAnnotation(value = "abc")
是一样的!!!千万要注意的是,只有在方法名是
value
并且它是唯一的一个需要赋值的注解元素的时候才能这么用。如果我们也要给otherMethod
赋值的话,那就必须显式的写出来,就像这样:public class Test {
@ValueAnnotation(value = "abc", otherMethod = 6)
public void method();
}
元注解
我们上边定义的CellFormat
注解可以用,但是有个问题,就是这个注解可以加在代码的任何部分:
@CellFormat //加在了类名上头
public class Action {
@CellFormat //加在了方法上头
@FacialExpression
public void laugh() {
System.out.println("laughing");
}
// ... 为节省篇幅,省略其他代码
}
这就不好了,我们只想把它加在需要转成Excel表格的字段上呀,所以我们需要规定一下这个注解的使用范围。设计java的大叔们想到了这个问题,并且还想到了别的问题,所以他们提供了4种描述注解的注解,所以也叫元注解
。我们可以在自定义注解的时候用他们提供的这些元注解
来注解我们的自定义注解,哈哈,有点儿扰,其实就像是这样:
@元注解1
@元注解2
...
public @interface 自定义注解名 {
注解元素1
注解元素2
...
}
这些元注解
是java语言自带的,我们看看有哪些,分别有啥用:
@Target
这个元注解中只有一个方法:
ElementType[] value();
而
ElementType
是一个枚举类型,每个枚举对象代表了java代码的某个部分,我们需要了解下边这几种枚举对象:ElementType.CONSTRUCTOR
:用于描述构造方法ElementType.FIELD
:用于描述字段ElementType.LOCAL_VARIABLE
:用于描述方法体中的局部变量ElementType.METHOD
:用于描述方法ElementType.PACKAGE
:用于描述包ElementType.PARAMETER
:用于描述方法参数ElementType.TYPE
:用于描述类、接口(包括注解类型) 或enum声明ElementType.ANNOTATION_TYPE
:用于描述注解
因为
Target
中只有一个value
方法,所以我们可以在@Target
后的小括号()
里直接填上注解元素的值,又由于value
方法的返回值是一个数组,所以需要使用中括号把这些枚举对象扩起来。比如我们想让某个自定义注解应用在构造方法
、方法
和字段
上,我们就可以在自定义注解上边加上:@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD})
而对于我们前边定义的
CellFormat
注解来说,我们只想让它用在字段上,所以可以这么写:ElementType和Target都定义在`java.lang.annotation`包里,所以使用的时候需要import一下
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target({ElementType.FIELD})
public @interface CellFormat {}
因为大括号
{}
里边只有一个元素,在这种情况下我们也可以把大括号{}
省略掉,写成这样:@Target(ElementType.METHOD)
public @interface FacialExpression {}
此时如果我们再在非字段的地方上加上这个注解,就会编译报错了:
@FacialExpression //编译错误❌
public class Action {
// ... 为节省篇幅,省略其他代码
}
@Retention
我们写的java代码会经过3个阶段:
- 源代码阶段,也就是
.java
文件。 - class文件阶段。
- 虚拟机加载class文件到内存里,执行代码,这个阶段也叫
运行时
。
元注解
@Retention
中也只有一个value
方法:RetentionPolicy value();
而
RetentionPolicy
也是一个枚举类型,它的三个枚举对象分别代表了注解
在这3个阶段里是否被保留。RetentionPolicy.SOURCE
:在源文件中保留。RetentionPolicy.CLASS
:在class文件中保留RetentionPolicy.RUNTIME
:在运行时保留,也就是可以通过反射的方式获取到这种类型的注解。这种方式也是我们自定义注解的一般采用方式~
比如我们在某个
注解
上头这么声明:@Retention(RetentionPolicy.SOURCE)
那么这个注解只能在java源文件中看到,一旦经过编译,产生
.class
文件后,便找不到注解的踪影了。现在我们把我们的
CellFormat
的生命周期定义成RetentionPolicy.RUNTIME
的,也就是运行时也能获取注解信息:import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface CellFormat {
String format();
boolean isBold() default false;
int fontSize() default 12;
String align() default "left";
}
然后我们就可以在
运行时
通过反射方法获取到这些注解的信息咧:public class Test {
public static void main(String[] args) {
Class<Employee> clazz = Employee.class;
try {
Field field = clazz.getDeclaredField("name");
Annotation[] annotations = field.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
}
执行结果是:
@CellFormat(align=center, isBold=true, fontSize=14, format=数值)
更多关于获取注解信息的反射方法我们稍后再说哈~
- 源代码阶段,也就是
@Documented
这个注解主要是和生成java文档有关的,如果在某个注解上加了这个元注解,则当该注解加在某段代码上时,使用
javadoc
工具会把注解也收录到文档中,默认的是不收录的。这个注解只有我们去生成注释文档时才有用,如果不生成的话,就不需要关心它。@Inherited
如果在某个注解上加了这个元注解,则当该注解加在某个类上时,该类的子类也会继承到这个注解。详细使用情况我们稍后介绍~
注解解析器
上边我们使用java给我们提供的元注解
成功的定义出了一个名叫CellFormat
的注解,并且成功的给Action
类的各个字段添加了这个注解。但是有啥用呢?
是的,啥用都没有,注解的添加不会影响原先代码的执行。因为它只是一种描述数据,所以谁关心,谁处理。要是没人关心放在那也没关系,等以后哪天有人想关了再分析代码呗~所以如果没有工具来读取注解,那么注解也不会比注释更有用。
对于元注解来说,由于它们是java代码自己提供的,所以编译器
或者java自带的一些工具会关心并解析这些元注解,比如某个注解上加了@Target
,编译器会判断这个注解是否用到了合适的代码部分,如果不对会编译错误。而对于我们自定义的注解来说,注解并不会影响到编译器行为,对代码是没有任何影响的!
如果我们想处理一下我们的自定义注解,就需要了解一下关于注解的反射方法。
注解反射方法
设计java的大叔们在java.lang.reflect
包中为我们定义了一个AnnotatedElement
的接口:
public interface AnnotatedElement {
<T extends Annotation> T getAnnotation(Class<T> annotationClass);
Annotation[] getAnnotations();
Annotation[] getDeclaredAnnotations();
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
}
因为我们可以把注解
放在代码的不同部分上,比如字段、方法、包名、类名啥的,所以代表不同代码部分的反射类,诸如Class
、Constructor
、Field
、Method
、Package
都实现了AnnotatedElement
接口。这些方法的具体作用是:
方法 | 用途 |
---|---|
getAnnotation |
返回该程序元素上存在的、指定类型的注解(包括通过继承的来的),如果该类型注解不存在,则返回null。 |
getAnnotations |
返回该程序元素上存在的所有注解(包括通过继承的来的)。 |
getDeclaredAnnotations |
判断该程序元素上是否包含指定类型的注解(不包括通过继承的来的),存在则返回true,否则返回false。 |
isAnnotationPresent |
返回直接存在于此元素上的所有注解(不包括通过继承的来的)。 |
在测试这些方法之前把我们前边定义的两个注解再抄一遍:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface CellFormat {
String format();
boolean isBold() default false;
int fontSize() default 12;
String align() default "left";
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface FacialExpression {
}
然后再定义一个通用的注解,并且它可以被继承:
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface FullAnnotation {
}
我们修改一下之前的Employee
类,给类名上也加上注解:
@FullAnnotation
public class Employee {
@CellFormat(format = "数值", isBold = true, fontSize = 14, align = "center")
private String name; //姓名
@CellFormat(format = "数值")
private int age; //年龄
@CellFormat(format = "文本")
private String id; //身份证号
@ValueAnnotation(value = "23", otherMethod = 1)
@CellFormat(format = "货币", isBold = true)
private float salary; //工资,单位:元
// ... 为节省篇幅,省略字段的getter/setter方法和构造方法
@FacialExpression
public void laugh() {
System.out.println("laughing");
}
@FacialExpression
public void cry() {
System.out.println("crying");
}
public void run() {
System.out.println("running");
}
public void walk() {
System.out.println("standing");
}
}
再定义一个继承自Employee
的类:
public class SubEmployee extends Employee {}
然后我们以Class
、Method
、Field
为例来测试一下AnnotatedElement
中声明的这些方法:
public static void main(String[] args) {
Class<Employee> clazz = Employee.class;
Annotation annotation = clazz.getAnnotation(FullAnnotation.class);
System.out.println("通过getAnnotation方法获取Employee类上FullAnnotation类型的注解:" + annotation);
Annotation[] annotations = clazz.getAnnotations();
System.out.println("通过getAnnotations方法获取用于Employee类的注解数组:" + Arrays.toString(annotations));
Annotation[] declaredAnnotations = clazz.getDeclaredAnnotations();
System.out.println("通过getDeclaredAnnotations方法获取用于Employee类的注解数组:" + Arrays.toString(declaredAnnotations));
boolean isAnnotationPresent = clazz.isAnnotationPresent(FullAnnotation.class);
System.out.println("Employee类上是否有FacialExpression的注解:" + isAnnotationPresent);
System.out.println("---------");
try {
Field field = clazz.getDeclaredField("name");
Annotation fieldAnnotation = field.getAnnotation(CellFormat.class);
System.out.println("通过getAnnotation方法获取name字段上CellFormat类型的注解:" + fieldAnnotation);
Annotation[] fieldAnnotations = field.getAnnotations();
System.out.println("通过getAnnotations方法获取用于name字段的注解数组:" + Arrays.toString(fieldAnnotations));
Annotation[] fieldDeclaredAnnotations = field.getDeclaredAnnotations();
System.out.println("通过getDeclaredAnnotations方法获取用于name字段的注解数组:" + Arrays.toString(fieldDeclaredAnnotations));
boolean isFieldAnnotationPresent = field.isAnnotationPresent(CellFormat.class);
System.out.println("name字段上是否有CellFormat的注解:" + isFieldAnnotationPresent);
System.out.println("---------");
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
try {
Method method = clazz.getDeclaredMethod("laugh");
Annotation methodAnnotation = method.getAnnotation(FacialExpression.class);
System.out.println("通过getAnnotation方法获取laugh方法上CellFormat类型的注解:" + methodAnnotation);
Annotation[] methodAnnotations = method.getAnnotations();
System.out.println("通过getAnnotations方法获取用于laugh方法的注解数组:" + Arrays.toString(methodAnnotations));
Annotation[] methodDeclaredAnnotations = method.getDeclaredAnnotations();
System.out.println("通过getDeclaredAnnotations方法获取用于laugh方法的注解数组:" + Arrays.toString(methodDeclaredAnnotations));
boolean isMethodAnnotationPresent = method.isAnnotationPresent(FacialExpression.class);
System.out.println("laugh方法上是否有FacialExpression的注解:" + isMethodAnnotationPresent);
System.out.println("---------");
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
Class<SubEmployee> clz = SubEmployee.class;
Annotation subAnnotation = clz.getAnnotation(FullAnnotation.class);
System.out.println("通过getAnnotation方法获取SubEmployee类上FullAnnotation类型的注解:" + subAnnotation);
Annotation[] subAnnotations = clz.getAnnotations();
System.out.println("通过getAnnotations方法获取用于SubEmployee类的注解数组:" + Arrays.toString(subAnnotations));
Annotation[] subDeclaredAnnotations = clz.getDeclaredAnnotations();
System.out.println("通过getDeclaredAnnotations方法获取用于SubEmployee类的注解数组:" + Arrays.toString(subDeclaredAnnotations));
boolean isSubAnnotationPresent = clz.isAnnotationPresent(FullAnnotation.class);
System.out.println("SubEmployee类上是否有FacialExpression的注解:" + isSubAnnotationPresent);
}
执行结果是:
通过getAnnotation方法获取Employee类上FullAnnotation类型的注解:@FullAnnotation()
通过getAnnotations方法获取用于Employee类的注解数组:[@FullAnnotation()]
通过getDeclaredAnnotations方法获取用于Employee类的注解数组:[@FullAnnotation()]
Employee类上是否有FacialExpression的注解:true
---------
通过getAnnotation方法获取name字段上CellFormat类型的注解:@CellFormat(align=center, fontSize=14, isBold=true, format=数值)
通过getAnnotations方法获取用于name字段的注解数组:[@CellFormat(align=center, fontSize=14, isBold=true, format=数值)]
通过getDeclaredAnnotations方法获取用于name字段的注解数组:[@CellFormat(align=center, fontSize=14, isBold=true, format=数值)]
name字段上是否有CellFormat的注解:true
---------
通过getAnnotation方法获取laugh方法上CellFormat类型的注解:@FacialExpression()
通过getAnnotations方法获取用于laugh方法的注解数组:[@FacialExpression()]
通过getDeclaredAnnotations方法获取用于laugh方法的注解数组:[@FacialExpression()]
laugh方法上是否有FacialExpression的注解:true
---------
通过getAnnotation方法获取SubEmployee类上FullAnnotation类型的注解:@FullAnnotation()
通过getAnnotations方法获取用于SubEmployee类的注解数组:[@FullAnnotation()]
通过getDeclaredAnnotations方法获取用于SubEmployee类的注解数组:[]
SubEmployee类上是否有FacialExpression的注解:true
需要特别注意的是,由于FullAnnotation
上被@Inherited
注解了,所以被@FullAnnotation
注解的类的子类也会继承这个注解,也就是说SubEmployee
也会继承到@FullAnnotation
这个注解,调用getAnnotation
、getAnnotations
可以获取到这个注解,但是getDeclaredAnnotations
和isAnnotationPresent
并不能获取到这个注解,大家需要注意到这个不同。
在知道了如何使用反射方法去获取注解信息后,我们想分析一下Employee
类有哪些关于面部表情的方法,就可以这么写:
public class Test {
public static void main(String[] args) {
Class<Employee> clazz = Employee.class;
Method[] methods = clazz.getDeclaredMethods();
List<String> annotationMethodNameList = new ArrayList<>();
for (Method method : methods) {
Annotation annotation = method.getAnnotation(FacialExpression.class);
if (annotation != null) {
annotationMethodNameList.add(method.getName());
}
}
System.out.println("Action类标记了FacialExpression的方法有:" + annotationMethodNameList);
}
}
执行结果是:
Action类标记了FacialExpression的方法有:[laugh, cry]
如果你还想按照自己的需求去解析一下,那就自己写点代码去解析吧~
再一次提醒,编译器
是不关心我们的自定义注解的,如果想处理,需要另外再写代码分析,这也是为啥我们的自定义注解的生命周期一般都是运行时
的。那能不能让编译器
去处理我们的自定义注解呢?
答:嗯,可以,你需要去修改一下编译过程,也就是需要定制一下编译器,这玩意儿可以做,但是我不会~
内置注解
java中自带了3种定义好的注解,也叫内置注解
。这三种注解都会被编译器
关注,也就是说这些注解会对编译造成影响。
Override
看一下这个注解的定义:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {}
说明
Override
只能标记在方法上头,并且只对源代码有效,也就是说编译成.class
文件就看不见这个注解了,它的使用方法我们已经见了很多遍了:public class Father {
public void play() {
System.out.println("打麻将");
}
}
public class Child extends Father {
@Override //表示这个方法是覆盖父类中的
public void play() {
System.out.println("玩尿泥");
}
}
编译器
在分析源代码的时候,如果某个方法上编标记了这个注解,编译器
会去查找它的父类中是否有同样的方法,如果没有的话,编译器就会报错来提醒我们程序员写错啦!@SuppressWarnnings
看一下它的定义:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
这个注解可以用在类、字段、方法、方法参数、构造方法以及局部变量上,也是只对源代码有效。
如果某段代码上可能有些不安全的操作,比方说我们前边遇到的将某个类型转型为泛型类型,
编译器
会发出警告。但是如果在发出警告的代码上加了这个注解,编译器
就不会再发出警告提示了。大家看到这个注解里有一个
value
方法,它的返回类型是一个数组,数组元素的取值可以是下边这些,代表着屏蔽不同类型的警告:deprecation
:使用了不赞成使用的类或方法时的警告;unchecked
:执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型;fallthrough
:当 Switch 程序块直接通往下一种情况而没有 Break 时的警告;path
:在类路径、源文件路径等中有不存在的路径时的警告;serial
:当在可序列化的类上缺少 serialVersionUID 定义时的警告;finally
:任何 finally 子句不能正常完成时的警告;all
:关于以上所有情况的警告。
上边我们最熟悉的要数
unchecked
警告了哈。因为value
方法的返回类型是一个数组,所以在使用的时候需要把各个元素用大括号{}
扩起来,比如我们要屏蔽path
和unchecked
警告,就可以写成这样:@SuppressWarnnings({"path", "unchecked"})
如果只屏蔽1个警告,那可以省略大括号:
@SuppressWarnnings("unchecked")
@Deprecated
看一下它的定义:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {}
这种注解可以作用在代码的所有部分处,然后它在
运行时
还可以被反射方法获取到。这个注解表明某段代码不鼓励被使用,比如采用了比较慢的实现算法,或者可能引发某种危险啥的,总之最好别用。比如我们加在
Employee
的walk
方法上:public class Employee {
// ... 为节省篇幅,省略其他方法和字段
@Deprecated
public void walk() {
System.out.println("standing");
}
然后在别处调用这个方法的时候编译器就会有
警告
:public class Test {
public static void main(String[] args) {
new Employee().walk();
}
}
执行
javac Test.java
的时候会发出警告:注: Test.java使用或覆盖了已过时的 API。
注: 有关详细信息, 请使用 -Xlint:deprecation 重新编译。
再次强调一遍,这3个注解是java内置的注解,编译器
会关心并处理它们。但是对于我们的自定义注解,编译器
可不会关心,如果我们需要处理,需要自己写程序去处理的。
总结
- 元数据就是对数据的描述数据。
- 注解是java代码各部分的
元数据
。 - 注解本质是一种继承自
java.lang.Annotation
的接口,它的每一个方法称为一个注解元素。 注意事项:
- 每个注解元素必须是
public
的,如果不填访问权限则默认是public
的。 方法返回类型必须是下边的这些类型:
- 所有基本数据类型(
int
、float
、boolean
、byte
、double
、char
、long
、short
) String
类型Class
类型enum
类型Annotation
类型,它是所有注解的祖宗接口- 以上所有类型的数组
- 所有基本数据类型(
如果注解的方法名是
value
,并且它是唯一的一个需要赋值的注解元素的时候,则可以在使用时在小括号()
中直接填写值。
- 每个注解元素必须是
java提出了4种元注解来描述自定义注解:
@Target
@Retention
@Documented
@Inherited
java自带了3种注解,分别是:
@Override
@SuppressWarnnings
@Deprecated
java在
AnnotatedElement
接口中提供了一些获取注解信息的反射方法:方法 用途 getAnnotation
返回该程序元素上存在的、指定类型的注解(包括通过继承的来的),如果该类型注解不存在,则返回null。 getAnnotations
返回该程序元素上存在的所有注解(包括通过继承的来的)。 getDeclaredAnnotations
判断该程序元素上是否包含指定类型的注解(不包括通过继承的来的),存在则返回true,否则返回false。 isAnnotationPresent
返回直接存在于此元素上的所有注解(不包括通过继承的来的)。 特别注意,java自带的3中注解和4种元注解都会被
编译器
关注并解析,而我们的自定义注解只是对代码的一种描述,本身一毛钱卵用也没有。如果你想使用,就编写自己的程序去解析,所以我们一般把注解的生命周期定义成运行时
的,去调用反射方法去分析注解。