0%

【转】Java注解基础学习

其实同class和interface一样,注解也属于一种类型。它是在Java SE 5.0版本中开始引入的概念。

注解的定义

注解通过 @ interface 关键字进行定义。

1
2
3
public @interface TestAnnotation {

}

它的形式跟接口很类似,不过前面加了@符号。 上面的代码就创建了一个名字为TestAnnotation的注解。

可以简单理解为创建了一张名字为TestAnnotation的标签

注解的应用

上面创建了一个注解,那么注解怎么使用呢?

1
2
3
4
@TestAnnotation
public class Test {

}

创建一个类 Test,然后在类上面加上 @ TestAnnotation 就可以用。
你可以简单理解为将 TestAnnotation 这个张标签贴到Test这个类上面。

不过,想要注解能够正常工作,还需介绍一下 元注解。

元注解

元注解是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其他的注解上面。

如果难于理解的话,可以这样理解。元注解就是一种标签,但是它是一张特殊的标签,
它的作用和目的就是给其他普通的标签进行解释说明的。

元注解有 @Retention、 @Documented、 @Target、 @Inherited、 @Repeatable 5种。

@Retention

Retention的英文意为保留期的意思。当@Retention 引用到一个注解上的时候,它解释了这个注解的存活时间。

源码如下:

1
2
3
4
5
6
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}

从源码可以看出,其有一个属性value,返回一个枚举 RetentionPolicy 类型,有3种类型:

RetentionPolicy.SOURCE: 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽略。

RetentionPolicy.CLASS:注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。

RetentionPolicy.RUNTIME:注解可以保留到程序运行的时候,它会被加载进入到JVM中,所以在程序运行时可以获取到它们。

我们可以通过这样的方式类加深理解,@Retention 去给一张标签解释的时候,它指定了这张标签的时间。@Retention相当于给一张标签上面盖了一张时间戳,时间戳指明了标签张贴的时间周期。

1
2
3
4
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation{

}

上面的代码中,我们指定了TestAnnotation 可以在程序运行周期被获取到,因次它的生命周期非常长。

@Document

顾名思义,这个元注解肯定和文档有关。它的作用是能够将注解中的元素包含到Javadoc中取。

@Target

Target是目标的意思,@Target指定了注解运用的地方。也就是指明,你的注解到底是用来修饰方法的?修饰类的?还是用来修饰字段属性的。

1
2
3
4
5
6
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}

从源码中可以看出,其有一个属性value,返回一个枚举 ElementType 类型的数组,这个数组的值就代表了可以在那些场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public enum ElementType {
/** 允许被修饰的注解作用在类、接口和枚举上 */
TYPE,

/** 允许作用在属性字段上,包括enum实例 */
FIELD,

/** 允许作用在方法上 */
METHOD,

/** 允许作用在方法参数上 */
PARAMETER,

/** 允许作用在构造器上 */
CONSTRUCTOR,

/** 允许作用在局部变量上 */
LOCAL_VARIABLE,

/** 允许作用在注解上(应用于另一个注解上) */
ANNOTATION_TYPE,

/** 允许作用在包上 */
PACKAGE,

/**
* 允许作用在类型参数声明(1.8新加入)
*/
TYPE_PARAMETER,

/**
* 允许作用在类型使用声明(1.8新加入)
*/
TYPE_USE
}

例如 @Override 注解使用了@Target(ElementType.METHOD),意味着,它只能注解方法,不能注解属性或者类,或者其他情况。
当未指定Target值时,则可以用于任何元素上,多个值则使用{}包含并用 “,”隔开,比如:
@Target(value={CONSTRUCTOR,FIELD,METHOD}
该注解既可注解构造方法、字段和方法。

@Inherited

Inherited是继承的意思,其让被修饰的注解拥有被继承的能力。

一个被@Inherited注解了的注解 修饰了一个父类,如果他的子类没有被其他注解修饰,则他的子类也继承了父类的注解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/** 自定义注解*/
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface TestAnnotation{

}

/** 父类标注 自定义注解 */
@TestAnnotation
public class Father{

}
/** 子类 */
public class Son extentx Father{

}
/** 测试子类获取父类自定义注解*/
public class Test{
public static void main(String[] args){
//获取Son的class对象
Class<Son> sonClass = Son.class;
//获取Son类上的注解TestAnnotation可以执行成功
TestAnnotation annotation = sonClass.getAnnotation(TestAnnotation.class);
}
}

注解TestAnnotation被@Inherited修饰,之后类Father被TestAnnotation注解,类Son继承了Father类,类Son也就拥有TestAnnotation这个注解。

可以这样理解:

老子非常有钱,所以人们给他贴了一张标签叫做富豪。

老子的儿子长大后,只要没和老子断绝父子关系,虽然别人没给他贴富豪的标签,但他自然也是富豪。

老子的孙子长大后,自然也是富豪。

这就是人们口中戏称的富一代,富二代,富三代。虽然叫法不同,好像好多个标签,但其实事情的本质也就是他们有一张共同的标签,也就是老子身上的那张富豪的标签。

@Repeatable

Repeatable是可重复的意思,@Repeatable是Java1.8才加进来的,所有算是一个新的特效。

别这个元注解修饰的注解 可以多次修饰同一个对象,但是每次注解又代表不同的含义。
比如:一个人既是程序员,又是成品经理,同时也是画家

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/** 定义一个容器注解*/
@interface Persons{
Person[] value();
}

/** 被Repeatable修饰的注解 */
@Repeatable(Persons.class)
@interface Person{
String role() default "";
}

@Person(role="artist")
@Person(role="coder")
@Person(role="PM")
class SuperMan{

}

上面的代码@Repeatable注解了Person。而@Repeatable后面括号中的类相当于一个容器注解。

什么是容器注解呢?就是存放其他注解的地方。他本身也是一个注解。

容器注解,他里面必须要有一个value的属性,注意它是数组。

如果不好理解的话,可以这样理解。Persons 是一张总的标签,上面贴满了Person这种类型但内容不一样的标签。把Person给SuperMan贴上,相当于同时给他贴上了程序员、产品经理、画家的标签。

我们可能对于@Person(role=”PM”) 括号里的内容感兴趣,他其实就是Person这个注解的role属性赋值为PM。

注解的属性

注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。

注解本质上就是一个Annotation接口

1
2
3
4
5
6
7
8
9
10
11
/** Annotatioin 接口源码*/
public interface Annotation{

boolean equals(Object obj;

int hashCode();

String toString();

Class<? extends Annotation> annotationType();
}

通过以上源码,我们知道注解本身就是Annotation接口的子接口,也就是说注解其实可以有属性和方法,但是接口的数据都是static final的,对于注解来说没有什么意义,而我们定义的接口方法就相当于是注解的属性,也就对应了前面所说的为什么注解只有属性成员变量,其实他就是接口的方法,这就为什么成员变量会有方法,不同于接口的是我们可以在注解的括号中给成员变量赋值。

1
2
3
4
5
6
7
8
9
@Target(ElementType.TYPE)
@Retention(RetentioniPolicy.RUNTIME)
public @interface TestAnnotation{
String name() default "zk";
int age() default 18;
}
@TestAnnotation(name="zhangke",age="25")
public class Person{
}

上面代码定义了TestAnnotation 这个注解拥有name和age两个属性。用default 指定默认值。如果有default设置了默认值时,也可以无需在TestAnnotation后面的括号中进行赋值。

1
2
3
@TestAnnotation()
public class Person{
}

赋值的方式就是在注解的括号中用“,”隔开分别给对应的属性赋值。
如果注解的只有一个属性时,可以直接把属性值写到括号中。

1
2
3
4
5
6
7
8
@Target(ElementType.TYPE)
@Retention(RetentioniPolicy.RUNTIME)
public @interface TestAnnotation{
String name();
}
@TestAnnotation(zhangke)
public class Person{
}

需要注意的是,在注解中定义属性时有一下几种的类型:
1、基本数据类型
2、String
3、枚举
4、注解
5、类、接口
6、以上类型的一维数组类型

Java 内置的注解

Java内置的直接共有5个
@Override:
这个大家很熟悉了,让编译器检查被标记的方法,保证其重写了父类的某一个方法。此注解只能标记方法。

1
2
3
4
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

@Deprecated:
标记某些程序元素已经过时,程序员请不要再使用了

1
2
3
4
5
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

@SuppressWarnings:
阻止警告的意思。调用了被@Deprecated注解后的方法,编译器会警告提醒,但是开发者不想看到这些警告,可以使用@SuppressWarnings达到目的

1
2
3
4
5
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}

其内部有个String数组类型的属性,根据传入的值来取消相应的警告:
deprecation:使用了不赞成使用的类或方法时的警告;
unchecked:执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型;
fallthrough:当 Switch 程序块直接通往下一种情况而没有 Break 时的警告;
path:在类路径、源文件路径等中有不存在的路径时的警告;
serial:当在可序列化的类上缺少 serialVersionUID 定义时的警告;
finally:任何 finally 子句不能正常完成时的警告;
all:关于以上所有情况的警告

@SafeVarargs(Java7 新增):
@SuppressWarnings可以用在各种需要取消警告的地方,而 @SafeVarargs主要用在取消参数的警告。就是说编译器如果检查到你对方法参数的操作,有可能发生问题时会给出警告,但是你很自(任)性,老子不要警告,于是你就加上了这个标签。

1
2
3
4
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {}

@FunctionalInterface(Java8 新增):
标记型注解,告诉编译器检查被标注的接口是否是一个函数接口,即检查这个接口是否只包含一个抽象方法,只有函数接口才可以使用Lambda表达式创建实例。

如何使用注解

前面我们学习了注解的基本语法,那注解具体怎么使用呢?
这时候就需要用到APT(Annotation Processing Tool),访问和处理Annotation的工具,那么APT是具体怎么读取注解的属性信息的呢,那就是反射。
Annotation接口是所有注解的父接口(需要通过发编译查看),在java.lang.reflect发射包下存在一个叫AnnotationElement的接口,其表示程序中可以接受注解的程序元素,比如类,方法,字段,构造方法,包等等。而Java为使用反射的主要类实现了此接口,如Class类、Field类、Method类、Constructor类等。

当我们通过反射技术获取到反射包内的那些类型的实例后,就可以通过AnnotationElement接口的API方法来获取注解信息了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17


/**是否存在对应 Annotation 对象*/
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
return GenericDeclaration.super.isAnnotationPresent(annotationClass);
}

/**获取 Annotation 对象*/
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
Objects.requireNonNull(annotationClass);

return (A) annotationData().annotations.get(annotationClass);
}
/**获取所有 Annotation 对象数组*/
public Annotation[] getAnnotations() {
return AnnotationParser.toArray(annotationData().annotations);
}

下面结合前面的例子,我们来获取下注解的属性,在获取之前我们自定义的注解必须使用元注解@Retention(RetentionPolicy.RUNTIME)。

待续…