注解通过 @interface 关键字进行定义。上面我们还看到了有@Target,@Retention标记,那这些又是什么呢,其实它们是元注解,那什么又是元注解呢?先往下看,这些后面会讲。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView {
int value();
}
注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。
注解中属性可以有默认值,默认值需要用 default 关键字指定。下面我们看如何声明注解的属性:
public @interface TestAnnotation {
String id() default "-1";
String name();
}
注意,在注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组。
上面的注解定义了id, name两个属性,那这个注解怎么使用呢?在使用的时候我们需要给所有的属性都赋值,如果属性有默认值的我们可以不赋值,如下:
// id有默认值, 可以不赋值
@TestAnnotation(name = "test")
public class TestUseAnnotation {
}
// 也可以都重新赋值:
@TestAnnotation(id = "test", name = "test")
public class TestUseAnnotation {
}
另外,还有一种情况。如果一个注解内仅仅只有一个属性并且名字是value(必须是value)时,应用这个注解时可以直接把属性值填写到括号内。
public @interface TestSingleAnnotation {
String value() default "-1";
}
如上面只有一个value属性,所以可以像下面这样用:
// 简写
@TestSingleAnnotation("1")
public class TestUseAnnotation {
}
// 全写:
@TestSingleAnnotation(value = "1")
public class TestUseAnnotation {
}
还有一种情况是一个注解没有任何属性。比如:
public @interface TestEmptyAnnotation {
}
那么,在使用的时候我们可以省略注解后面的括号,如下:
@TestEmptyAnnotation
public class TestUseAnnotation {
}
编译器对元素的默认值有些过分挑剔。首先,元素不能有不确定的值。也就是说,元素必须要么具有默认值,要么在使用注解时提供元素的值。其次,对于非基本类型的元素,无论是在源代码中声明,还是在注解接口中定义默认值,都不能以null作为值,这就是限制。为了绕开这个限制,只能定义一些特殊的值,例如空字符串或负数,表示某个元素不存在。
注解是不支持继承的,因此不能使用关键字extends来继承某个@interface,但注解在编译后,编译器会自动继承java.lang.annotation.Annotation接口
元注解是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其它的注解上面。如果说注解是对程序代码的解释说明,那元注解就是对注解的解释说明。
元注解有 @Retention、@Documented、@Target、@Inherited,@Repeatable 五种,其中@Repeatable是Java8开始支持的。
当@Retention 应用到一个注解上的时候,它用来约束注解的生命周期,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
它的取值有三种:
它的作用是能够将注解中的元素包含到 Javadoc 中去。
当@Target 应用到一个注解上的时候,它标记这个注解应该可以作用在哪些 Java 成员上,比如只能作用到方法上、类上、方法参数上等等。@Target 有这些值:
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE, //类,接口,注解,枚举
/** Field declaration (includes enum constants) */
FIELD, //字段
/** Method declaration */
METHOD, //方法
/** Formal parameter declaration */
PARAMETER, //参数
/** Constructor declaration */
CONSTRUCTOR, //构造方法
/** Local variable declaration */
LOCAL_VARIABLE, //局部变量
/** Annotation type declaration */
ANNOTATION_TYPE, //注解
/** Package declaration */
PACKAGE, //包
/**
* Type parameter declaration
*
* @since 1.8
* @hide 1.8
*/
TYPE_PARAMETER, //表示注解能写在类型变量的声明语句中(如:class MyClass {...})
/**
* Use of a type
*
* @since 1.8
* @hide 1.8
*/
TYPE_USE //表示注解能写在使用类型的任何语句中(例如声明语句、泛型和强制转换语句中的类型)。
}
Inherited 是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Test {}
@Test
public class A {}
public class B extends A {} // A被Test注解,因为Test有@Inherited修饰,B继承A, 那么B也被Test注解了。
标识某注解可以在同一个声明上使用多次。什么样的注解会多次应用呢?通常是注解的值可以同时取多个。
如每个设备都有功能,但手机这个设备有多个功能,手机既可以打电话,又可以浏览网页,又可以照相。
@interface Devices {
Device[] value();
}
@Repeatable(Devices.class)
@interface Device{
String fuc() default "";
}
@Device(func="call_phone")
@Device(func="view_web_pages")
@Device(func="take_photo")
public class Phone{
}
学习了上面相关的知识,我们已经可以自己定义一个注解了。其实 Java 语言本身已经提供了一些现成的注解,我们平常写代码的过程中也经常遇到。
@FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。
函数式接口 (Functional Interface) 就是一个具有一个方法的普通接口。
比如我们进行线程开发中常用的 Runnable 就是一个典型的函数式接口,通过下面的源码可以看到它就被 @FunctionalInterface 注解。
那函数式接口标记有什么用呢?其实是函数式接口可以很容易转换为 Lambda 表达式。
@FunctionalInterface
public interface Runnable {
void run();
}
一般注解都是和反射配合起来使用的,我们知道Java所有注解都继承了Annotation接口,也就是说 Java使用Annotation接口代表注解元素,该接口是所有Annotation类型的父接口。同时为了运行时能准确获取到注解的相关信息,Java在java.lang.reflect 反射包下新增了AnnotatedElement接口,它主要用于表示目前正在 JVM 中运行的程序中已使用注解的元素,通过该接口提供的方法可以利用反射技术地读取注解的信息,如反射包的Constructor类、Field类、Method类、Package类和Class类都实现了AnnotatedElement接口。
下面是AnnotatedElement中相关的API方法,以上5个类都实现以下的方法:
返回值 | 方法名称 | 说明 |
---|---|---|
|
getAnnotation(Class annotationClass) |
该元素如果存在指定类型的注解,则返回这些注解,否则返回 null。 |
Annotation[] |
getAnnotations() |
返回此元素上存在的所有注解,包括从父类继承的 |
boolean |
isAnnotationPresent(Class extends Annotation> annotationClass) |
如果指定类型的注解存在于此元素上,则返回 true,否则返回 false。 |
Annotation[] |
getDeclaredAnnotations() |
返回直接存在于此元素上的所有注解,注意,不包括父类的注解,调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响,没有则返回长度为0的数组 |
通过反射获得某个注解后,就可以获得这个注解的属性了,然后就可以写一个我们自己的运行时注解解析器了。
注解到底有什么用?
注解是一系列元数据,它提供数据用来解释程序代码,但是注解并非是所解释的代码本身的一部分。注解对于代码的运行效果没有直接影响。
注解有许多用处,主要如下:
- 提供信息给编译器: 编译器可以利用注解来探测错误和警告信息
- 编译阶段时的处理: 软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理。
- 运行时的处理: 某些注解可以在程序运行的时候接受代码的提取
当开发者使用了Annotation 修饰了类、方法、Field 等成员之后,这些 Annotation 不会自己生效,必须由开发者提供相应的代码来提取并处理 Annotation 信息。这些处理提取和处理 Annotation 的代码统称为 APT(Annotation Processing Tool)。
https://blog.csdn.net/briblue/article/details/73824058
https://blog.csdn.net/javazejian/article/details/71860633