注解

注解概述

注解概述

  • 注解(annotation),是一种代码级别的说明,和类 接口平级关系.

    • 注解(Annotation)相当于一种标记,在程序中加入注解就等于为程序打上某种标记,以后,javac编译器、开发工具和其他程序可以通过反射来了解你的类及各种元素上有无标记,看你的程序有什么标记,就去干相应的事

    • 我们之前使用过的注解:

      1)@Override:子类重写方法时——编译时起作用

      2)@FunctionalInterface:函数式接口——编译时起作用

      3)@Test: 的测试注解——运行时起作用

注解的作用

  • 生成帮助文档@author和@version

  • 执行编译期的检查 例如:@Override

  • 框架的配置(框架=代码+配置)

    • 具体使用请关注框架课程的内容的学习。

小结

  1. 注解用在“源码中”,作为一个“标记”。给“注解解析器”看的,告诉“注解解析器”怎样编译、运行下面的代码。
  2. 开发中,我们一般都是使用注解

JDK提供的三个基本的注解

@Override:描述方法的重写.

@SuppressWarnings:压制\忽略警告.

@Deprecated:标记过时

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
@SuppressWarnings("all")
class Fu{
public void show(){

}
}
class Zi extends Fu{
@Override
public void show(){

}
}
public class Demo {
public static void main(String[] args) {
/*
JDK提供的三个基本的注解:
@Override:描述方法的重写.
@SuppressWarnings:压制\忽略警告.
@Deprecated:标记过时
*/
@SuppressWarnings("all")
int num;
}

@Deprecated
public static void method1(){

}

public static void method2(){

}
}

自定义注解

自定义注解语法

1
2
3
public @interface 注解名{
属性
}
  • 示例代码
1
2
3
4
5
6
7
/**
* 定义了注解
*
*/
public @interface Annotation01 {

}

注解属性

格式

  • 数据类型 属性名();

属性类型

​ 1.基本类型

​ 2.String

​ 3.Class类型

​ 4.注解类型

​ 5. 枚举类型

​ 6.以上类型的一维数组类型

  • 示例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public @interface Annotation01 {
// 1.基本数据类型(4类8种)
int a();
double b();

// 2.String类型
String c();

// 3.Class类型
Class d();

// 4.注解类型
Annotation02 f();

// 5.枚举类型
Sex e();
// 6.以上类型的一维数组类型
int[] g();
double[] h();
String[] i();
Sex[] j();
Annotation02[] k();
}

使用注解并给注解属性赋值

1
2
3
4
5
 使用注解:
如果一个注解中有属性,那么使用注解的时候一定要给注解属性赋值
如果一个注解没用属性,那么就不需要给注解属性赋值,直接使用即可
如何给注解属性赋值:
@注解名(属性名=值,属性名2=值2)

案例演示

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
36
37
38
public @interface MyAnnotation1 {
// 不带属性的注解
}



public @interface MyAnnotation2 {
// 带属性的注解
String name();
int age();
String[] arr();
}



@MyAnnotation1
@MyAnnotation2(name="张三",age=18,arr={"itheima","itcast"})
public class Test1 {
@MyAnnotation1
String str;

@MyAnnotation1
@MyAnnotation2(name="张三",age=18,arr={"itheima","itcast"})
public static void main(String[] args) {
/*
注解使用:
不带属性的注解:@注解名
带属性的注解: @注解名(属性名=属性值,属性名=属性值,...)

注意:带有属性的注解在使用的时候一定要给属性赋值,并且所有属性都要赋值
*/
@MyAnnotation1
@MyAnnotation2(name="张三",age=18,arr={"nbchen","zuoer"})
int num = 10;
}
}


给注解属性赋值的注意事项

  • 一旦注解有属性了,使用注解的时候,属性必须有值
  • 若属性类型是一维数组的时候,当数组的值只有一个的时候可以省略{}
  • 如果注解中只有一个属性,并且属性名为value,那么使用注解给注解属性赋值的时候,注解属性名value可以省略
  • 注解属性可以有默认值 格式:属性类型 属性名() defaul t 默认值;
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
public @interface MyAnnotation1 {
int a();
}

public @interface MyAnnotation2 {
int[] arr();
}

public @interface MyAnnotation3 {
int value();
}

public @interface MyAnnotation33 {
String[] value();
}

public @interface MyAnnotation4 {
int a() default 10;
}

public class Test {
public static void main(String[] args) {
/*
给注解属性赋值的注意事项:
- 一旦注解有属性了,使用注解的时候,属性必须有值
- 若属性类型是一维数组的时候,当数组的值只有一个的时候可以省略{}
- 如果注解中只有一个属性,并且属性名为value,那么使用注解给注解属性赋值的时候,注解属性名value可以省略
- 注解属性可以有默认值 格式:属性类型 属性名() defaul t 默认值;

*/
}

// 注解属性可以有默认值 格式:属性类型 属性名() defaul t 默认值;
//@MyAnnotation4
//@MyAnnotation4()
@MyAnnotation4(a = 100)
public static void method4(){

}

// 若属性类型是一维数组的时候,当数组的值只有一个的时候可以省略{}
//如果注解中只有一个属性,并且属性名为value,那么使用注解给注解属性赋值的时候,注解属性名value可以省略
//@MyAnnotation33(value={"nbchen","zuoer"})
//@MyAnnotation33(value={"nbchen"})
//@MyAnnotation33(value="nbchen")
@MyAnnotation33("itheima")
public static void method33(){

}

// 如果注解中只有一个属性,并且属性名为value,那么使用注解给注解属性赋值的时候,注解属性名value可以省略
//@MyAnnotation3(value=10)
@MyAnnotation3(10)
public static void method3(){

}

// 若属性类型是一维数组的时候,当数组的值只有一个的时候可以省略{}
// @MyAnnotation2(arr={10,20,30})
// @MyAnnotation2(arr={10})
@MyAnnotation2(arr=10)
public static void method2(){

}

// 一旦注解有属性了,使用注解的时候,属性必须有值
@MyAnnotation1(a = 10)
public static void method1(){

}

}

元注解

什么是元注解

​ 定义在注解上的注解

常见的元注解

@Target:表示该注解作用在什么上面(位置),默认注解可以在任何位置. 值为:ElementType的枚举值

  • METHOD:方法
  • TYPE:类 接口
  • FIELD:字段
  • CONSTRUCTOR:构造方法声明

@Retention:定义该注解保留到那个代码阶段, 值为:RetentionPolicy类型,默认只在源码阶段保留

  • SOURCE:只在源码上保留(默认)
  • CLASS:在源码和字节码上保留
  • RUNTIME:在所有的阶段都保留

.java (源码阶段) —-编译—> .class(字节码阶段) —-加载内存–> 运行(RUNTIME)

案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation1 {
// 限制该注解只能在方法上和类上使用
// 设置注解保留到运行阶段
}


@MyAnnotation1
public class Test {
//@MyAnnotation1 // 编译报错
int num;

@MyAnnotation1
public static void main(String[] args) {
//@MyAnnotation1 // 编译报错
String str;
}
}

注解解析

java.lang.reflect.AnnotatedElement接口: Class、Method、Field、Constructor等实现了AnnotatedElement

  • T getAnnotation(Class<T>annotationType):得到指定类型的注解引用。没有返回null。

  • boolean isAnnotationPresent(Class<?extends Annotation> annotationType):判断指定的注解有没有。

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation1 {
String name();

int age();

}


public class Test {

@MyAnnotation1(name="张三",age=18)
public void show1(){
System.out.println("show1方法执行了....");
}

public void show2(){
System.out.println("show2方法执行了....");
}

public static void main(String[] args) throws Exception{
/*
java.lang.reflect.AnnotatedElement接口: Class、Method、Field、Constructor等实现了AnnotatedElement
- T getAnnotation(Class<T> annotationType):得到指定类型的注解引用。没有返回null。
- boolean isAnnotationPresent(Class<?extends Annotation> annotationType):判断指定的注解有没有。

*/
// 需求:1.获取show1方法上面的注解对象
// 1.1 得到Test类的Class对象
Class<?> c = Class.forName("com.itheima.demo12_注解解析.Test");

// 1.2 获得show1方法的Method对象
Method show1M = c.getDeclaredMethod("show1");

// 1.3 根据Method对象调用getAnnotation()方法得到注解对象
MyAnnotation1 a1 = show1M.getAnnotation(MyAnnotation1.class);
System.out.println(a1.name());
System.out.println(a1.age());

System.out.println("======================");

// 2.需求: 判断某个方法上是否有MyAnnotation1注解
// 判断show1方法上是否有MyAnnotation1注解
boolean res1 = show1M.isAnnotationPresent(MyAnnotation1.class);
System.out.println(res1);// true

// 判断show2方法上是否有MyAnnotation1注解
Method show2M = c.getDeclaredMethod("show2");
boolean res2 = show2M.isAnnotationPresent(MyAnnotation1.class);
System.out.println(res2);// false

}
}

完成注解的MyTest案例

需求

​ 在一个类(测试类,TestDemo)中有三个方法,其中两个方法上有@MyTest,另一个没有.还有一个主测试类(MainTest)中有一个main方法. 在main方法中,让TestDemo类中含有@MyTest方法执行. 自定义@MyTest, 模拟单元测试.

思路分析

  1. 定义两个类和一个注解

  2. 在MainTest的main()方法里面:

1
2
3
4
//1.获得TestDemo字节码对象
//2.反射获得TestDemo里面的所有的方法
//3.遍历方法对象的数组. 判断是否有@MyTest(isAnnotationPresent)
//4.有就执行(method.invoke())

代码实现

  • MyTest.java
1
2
3
4
5
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {

}
  • TestDemo.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class TestDemo {
@MyTest
public void show1(){
System.out.println("show1方法执行了...");
}

@MyTest
public void show2(){
System.out.println("show2方法执行了...");
}

public void show3(){
System.out.println("show3方法执行了...");
}

}

  • MainTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class MainTest {

public static void main(String[] args) throws Exception {
// 让第一个类中含有@MyTest注解的方法执行
// 1.获取TestDemo类的字节码对象
Class<TestDemo> clazz = TestDemo.class;

// 2.使用字节码对象获取该类中所有方法对象
Method[] methods = clazz.getDeclaredMethods();

// 3.循环遍历所有方法对象
for (Method method : methods) {
// 4.在循环中,判断遍历出来的方法对象是否含有@MyTest注解
boolean res = method.isAnnotationPresent(MyTest.class);
if (res) {
// 5.如果有,就调用该方法执行
method.invoke(clazz.newInstance());
}
}

}

}