1 注解
1.1 什么是注解?
答:注解(Annotation)在JDK1.5之后增加的一个新特性,注解的引入意义很大,有很多非常有名的框架,比如Hibernate、Spring等框架中都大量使用注解。注解作为程序的元数据嵌入到程序,对运行代码的操作没有影响。注解可以被解析工具或编译工具解析,此处注意注解不同于注释(comment)。当一个接口直接继承java.lang.annotation.Annotation接口时,仍是接口,而并非注解。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
1.2 什么是元注解?
答:元注解就是可以注解到别的注解上的注解,如下的@Target。被注解的注解称之为组合注解,组合注解具备其上元注解的功能。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
1.3 什么是元数据?
答:元数据以标签的形式存在Java代码中,它的存在并不影响程序代码的编译和执行,通常它被用来生成其它的文件或运行时知道被运行代码的描述信息。Java当中的Javadoc和注解都属于元数据。
2 四种元注解
在JDK中提供了4个标准的用来对注解类型进行注解的注解类,我们称之为 meta-annotation(元注解),他们分别是:
- @Target:描述注解的使用范围(即被修饰的注解可以用在什么地方);
- @Retention:描述注解保留的时间范围(即:被描述的注解在它所修饰的类中可以被保留到何时);
- @Inherited:使被它修饰的注解具有继承性(如果某个类使用了被@Inherited修饰的注解,则其子类将自动具有该注解);
- @Documented:描述在使用 Javadoc 工具为类生成帮助文档时,保留其注解信息;
2.1 @Target注解
(1)作用是:描述注解的使用范围(即被修饰的注解可以用在什么地方)。
(2)目的是:用来说明那些被它所注解的注解类可修饰的对象范围:注解可以用于修饰 包、type(类、接口、枚举、注解类)、类成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数),在定义注解类时使用了@Target 能够更加清晰的知道它能够被用来修饰哪些对象,它的取值范围定义在ElementType 枚举中。
(3)Target注解的源码
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
public enum ElementType {
TYPE, // 类、接口、枚举类
FIELD, // 成员变量(包括:枚举常量)
METHOD, // 成员方法
PARAMETER, // 方法参数
CONSTRUCTOR, // 构造方法
LOCAL_VARIABLE, // 局部变量
ANNOTATION_TYPE, // 注解类
PACKAGE, // 可用于修饰:包
TYPE_PARAMETER, // 类型参数,JDK 1.8 新增
TYPE_USE // 使用类型的任何地方,JDK 1.8 新增
}
2.1.1 重复注解@Repeatable
(1)作用是:重复注解,允许在同一申明类型(类,属性,或方法)前多次使用同一个类型注解。
(2)在Java8以前,同一个程序元素前最多只能有一个相同类型的注解;如果需要在同一个元素前使用多个相同类型的注解,则必须使用注解“容器”。 Java8之前的做法:
public @interface Roles {
Role[] roles();
}
public @interface Roles {
Role[] value();
}
public class RoleAnnoTest {
@Roles(roles = {@Role(roleName = "role1"), @Role(roleName = "role2")})
public String doString(){
return "";
}
}
(3)Java8之后增加了重复注解,使用方式如下:
// @Repeatable,指向存储注解 Roles,在使用时候,直接可以重复使用 Role 注解
@Repeatable(Roles.class)
public @interface Role {
String roleName();
}
public @interface Roles {
Role[] value();
}
class RolesTest {
@Role(roleName = "role1")
@Role(roleName = "role2")
public String doString() {
return "";
}
}
不同的地方是,创建重复注解 Role 时,加上@Repeatable,指向存储注解 Roles,在使用时候,直接可以重复使用 Role 注解。从上面例子看出,Java 8里面做法更适合常规的思维,可读性强一点。但是,仍然需要定义容器注解。
2.1.2 类型注解
(1)TYPE_PARAMETER:表示该注解能写在类型参数的声明语句中。
(2)TYPE_USE:表示注解可以再任何用到类型的地方使用,无处不在的注解,可以让编译器执行更严格的代码检查,从而提高程序的健壮性:
创建对象(用 new 关键字创建)
类型转换
使用 implements 实现接口
使用 throws 声明抛出异常
(3)效果如下截图
2.2 @Retention注解
(1)作用是:描述注解保留的时间范围(即:被描述的注解在它所修饰的类中可以被保留到何时)。
(2)目的是:用来限定那些被它所注解的注解类在注解到其他类上以后,可被保留到何时,一共有三种策略,
@Retention(RetentionPolicy.SOURCE)
(3)三种策略定义在RetentionPolicy枚举中
public enum RetentionPolicy {
SOURCE, // 仅存在Java源文件
CLASS, // 存在经编译器后生成的Class字节码文件
RUNTIME // 保留在运行时VM中
}
- 生命周期长度:SOURCE < CLASS < RUNTIME ,前者能作用的地方后者一定也能作用;
- 如果需要在运行时通过反射去动态获取注解信息,那只能用 RUNTIME 注解;
- 如果要在编译时进行一些预处理操作,比如生成一些辅助代码,就用CLASS注解(比如ButterKnife的@BindView);
- 如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,则可选用 SOURCE 注解。
2.3 @Inherited
(1)作用是:使被它修饰的注解具有继承性(如果某个类使用了被@Inherited修饰的注解,则其子类将自动具有该注解)。
(2)通过代码来进行验证,创建一个自定义注解
@Target({ElementType.TYPE})
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface MyInherited {
}
@MyInherited
class MyInheritedParent {
}
class MyInheritedB extends MyInheritedParent {
}
执行方法,从控制台中可以看到打印的信息:
System.out.println(TAG + " " + MyInheritedParent.class.getAnnotation(MyInherited.class));
System.out.println(TAG + " " + MyInheritedB.class.getAnnotation(MyInherited.class));
// AnnotationParser @com.read.kotlinlib.annotation.MyInherited()
// AnnotationParser @com.read.kotlinlib.annotation.MyInherited()
2.4 @Documented
(1)作用是:描述在使用 Javadoc 工具为类生成帮助文档时,保留其注解信息。
(2)验证@Documented的作用,我们创建一个自定义注解和测试类
@Documented // @Documented
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface MyDocumentedt {
public String value() default "这是@Documented注解为文档添加的注释";
}
public class MyDocumentedTest {
/**
* 测试 document
* @return String the response
*/
@MyDocumentedt
public String test(){
return "测试document";
}
}
打开java文件所在的目录下,打开命令行输入:
javac .\MyDocumentedt.java .\MyDocumentedTest.java
javadoc -d doc .\MyDocumentedTest.java .\MyDocumentedt.java
打开生成的doc文件夹,打开index.html,发现在类和方法上都保留了 MyDocumentedt 注解信息
3 内建注解
Java提供了多种内建的注解,下面接下几个比较常用的注解:@Override(覆写)、@Deprecated(不赞成使用)、@SuppressWarnings(压制警告)这3个注解。
3.1 @Override(覆写)
// @Override可适用元素为方法,仅仅保留在java源文件中
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
用途:用于告知编译器,我们需要覆写超类的当前方法。如果某个方法带有该注解但并没有覆写超类相应的方法,则编译器会生成一条错误信息。
3.2 @Deprecated(不赞成使用)
// @Deprecated可适合用于除注解类型声明之外的所有元素,保留时长为运行时VM
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
用途:用于告知编译器,某一程序元素(比如方法,成员变量)不建议使用时,应该使用这个注解。Java在javadoc中推荐使用该注解,一般应该提供为什么该方法不推荐使用以及相应替代方法。
3.3 @SuppressWarnings(压制警告)
// @SuppressWarnings可适用于除注解类型声明和包名之外的所有元素,仅仅保留在java源文件中。
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
用途:用于告知编译器忽略特定的警告信息,例在泛型中使用原生数据类型。
该注解有方法value(),可支持多个字符串参数,例如:
@SupressWarning(value={"uncheck","deprecation"})
@Override,@Deprecated都是无需参数的,而@SuppressWarnings是需要带有参数的,可用参数如下:
4 自定义注解
(1)自定义注解
@Target({ElementType.TYPE, ElementType.METHOD})
// 由于该注解的保留策略为RetentionPolicy.RUNTIME,故可在运行期通过反射机制来使用,否则无法通过反射机制来获取
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthorAnno {
/**
* 注解方法不带参数,比如name(),website();
* 注解方法返回值类型:基本类型、String、Enums、Annotation以及前面这些类型的数组类型
* 注解方法可有默认值,比如default "chen.com",默认website="chen.com"
* @return
*/
String name();
String website() default "chen.com";
int revision() default 1;
}
(2)使用自定义注解的实体类
public class AnnotationDemo {
@AuthorAnno(name = "chen", website = "chen.com", revision = 1)
public static void main() {
System.out.println("I am main method");
}
@SuppressWarnings({"unchecked", "deprecation"})
@AuthorAnno(name = "chenDemo", website = "chenDemo.com", revision = 2)
public void demo() {
System.out.println("I am demo method");
}
}
(4)测试
public class AnnotationParser {
public final static String TAG = "AnnotationParser";
/**
* 解析注解
*/
public static void parser() {
String clazz = "com.read.kotlinlib.annotation.AnnotationDemo";
Method[] demoMethod = new Method[0];
try {
demoMethod = AnnotationParser.class.getClassLoader().loadClass(clazz).getMethods();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
for (Method method : demoMethod) {
if (method.isAnnotationPresent(AuthorAnno.class)) {
AuthorAnno authorInfo = method.getAnnotation(AuthorAnno.class);
System.out.println(TAG + " method: " + method);
System.out.println(TAG + " name= " + authorInfo.name() + " , website= " + authorInfo.website()
+ " , revision= " + authorInfo.revision());
}
}
// AnnotationParser ethod: public void com.read.kotlinlib.annotation.AnnotationDemo.demo()
// AnnotationParser ame= chenDemo , website= chenDemo.com , revision= 2
// AnnotationParser ethod: public static void com.read.kotlinlib.annotation.AnnotationDemo.main()
// AnnotationParser ame= chen , website= chen.com , revision= 1
}
}
(5)通过反射技术来解析自定义注解@AuthorAnno,关于反射类位于包java.lang.reflect,其中有一个接口AnnotatedElement,该接口定义了注释相关的几个核心方法,如下:
(6)通过getAnnotation获取注解的信息