JavaSE注解&反射

一、注解

1.注解的概念

在Java中注解其实就是写在接口、类、属性、方法上的一个标签,或者说是一个特殊形式的注释。但是,注释只是一个注释,而注解在代码运行时是可以被反射读取并进行相应的操作,而如果没有使用反射或者其他检查,那么注解是没有任何真实作用的,也不会影响到程序的正常运行结果。

2.注解的作用

  • 作为特定标记,用于告诉编译器一些信息,比喻@Override
  • 编译时动态处理,如动态生成代码,比如lombok的@Data
  • 运行时动态处理,作为额外信息的载体,如获取注解信息

3.注解的分类

  • 元注解 – 注解的注解,标明该注解的使用范围、生命周期等。
  • 标准注解 – Java提供的一些注解,标明过期的元素/标明是复写父类方法的方法/标明抑制警告。
  • 自定义注解 – 第三方定义的注解,含义和功能由第三方来定义和实现。

3.1元注解

用于定义注解的注解,通常用于注解的定义上,标明该注解的使用范围、生效范围等。

元注解有五种:@Retention、@Target、@Documented、@Inherited、@Repeatable

最常用的是@Retention、@Target

@Retention

标明自定义注解的生命周期

生命周期:源文件>Class文件 > 运行时数据 (SOURCE>CLASS>RUNTIME)

  • SOURCE 源代码java文件,生成的class文件中就没有该信息了
  • CLASS class文件中会保留注解,但是jvm加载运行时就没有了
  • RUNTIME 运行时,如果想使用反射获取注解信息,则需要使用RUNTIME,反射是在运行阶段进行反射的
1
2
3
@Retention(RetentionPolicy.SOURCE)
@Retention(RetentionPolicy.CLASS)
@Retention(RetentionPolicy.RUNTIME)

@Target

描述自定义注解的使用范围,允许自定义注解标注在哪些Java元素上(类、方法、属性、局部属性、参数…)

value是一个数组,可以有多个取值,说明同一个注解可以同时用于标注在不同的元素上。

value的取值:

说明
TYPE 类、接口、注解、枚举
FIELD 属性
METHOD 方法
PARAMETER 方法参数
CONSTRUCTOR 构造函数
LOCAL_VARIABLE 局部变量(如循变量、catch参数)
ANNOTATION_TYPE 注解
PACKAGE
TYPE_PARAMETER 泛型参数
TYPE_USE 任何元素

@Inherited
是否可以被标注类的子类继承。被@Inherited修饰的注解是具有继承性的,在自定义的注解标注到某个类时,该类的子类会继承这个自定义注解。这里需要注意的是只有当子类继承父类的时候,注解才会被继承。类实现接口,或者接口继承接口,都是无法获得父接口上的注解声明的,这些需要通过反射来获得父类的注解。

@Repeatable
是否可以重复标注。这个注解其实是一个语法糖,标注的是多个@MyAnnotation,其实会给我们返回一个@MyAnnotations,相当于是Java帮我们把重复的注解放入了一个数组属性中,所以只是一个语法糖而已。

@Documented
是否在生成的JavaDoc文档中体现,被标注该注解后,生成的javadoc中,会包含该注解。

3.2 标准注解

  • @Override 标记一个方法是覆写父类方法

  • @Deprecated 标记一个元素为已过期,避免使用。支持的元素类型为:CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE

  • @SuppressWarnings 不输出对应的编译警告。

3.3自定义注解

格式

1
2
3
4
public @interface 注解名 {
修饰符 返回值类型 属性名() 默认值;
......
}

注解的本质就是一个接口,并且继承了java.lang.annotation.Annotation,在程序运行时,JVM会为其生成对应的代理类。

返回值支持的类型:

  • 基本类型 int float boolean byte double char logn short
  • String
  • Class
  • Enum
  • Annotation
  • 以上所有类型的数组类型
1
2
3
4
5
6
7
8
9
// 保留至运行时
@Retention(RetentionPolicy.RUNTIME)
// 可以加在方法或者类上
@Target(value = {ElementType.TYPE,ElementType.METHOD})
public @interface RequestMapping {
public String method() default "GET";
public String path();
public boolean required();
}

二、反射

1.反射的概念

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法,这种动态获取、调用对象方法的功能称为java语言的反射机制。

通过一个类的对象,看到这个类的结构,形象的称之为反射

Snipaste_2024-08-11_16-32-53

2.反射的功能

在运行时判断任意一个对象所属的类

在运行时构造任意一个类的对象

在运行时判断任意一个类所具有的成员变量和方法

在运行时获取泛型信息

在运行时调用任意一个对象的成员变量和方法

在运行时处理注解

生成动态代理

……

虽然反射可以实现动态创建对象和编译,非常灵活,但是是一种解释操作,对性能有影响

3.核心API

1
2
3
4
java.lang.Class // 代表一个类
java.lang.reflect.Method // 代表类的方法
java.lang.reflect.Field // 代表类的成员变量
java.lang.reflect.Constructor // 代表类的构造器

3.1 Class对象

通过反射获取Class对象

1
2
3
4
5
6
Animal animal = new Animal();
Class c1 = animal.getClass(); // 通过对象获得
Class c2 = Animal.class; // 通过类获得,原理就是每个类都有一个静态变量,Class class int.class也可以
Class c3 = Class.forName("org.example.test.Animal"); // forName 包名 获得。最常见的方法
Class c4 = Integer.TYPE; //包装器类型都有一个属性TYPE
Class c5 = c1.getSuperclass(); //获得父类的Class对象

注意

一个类在内存中只有一个Class对象

一个类被加载后,类的整个结构都会被封装在Class对象中,通过这个class对象,就可以获得这个类的所有内容

有Class对象的类型

class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类。

interface:接口

[]:数组

enum:枚举

annotation:注解@interface

primitive type:基本数据类型

void void.class

3.2 Constructor

获取到Class对象后,我们就可以通过Class对象获取到该类的构造器了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//获得所有构造方法,返回Constructor对象数组
c1.getDeclaredConstructors();

//获得public构造方法,返回Constructor对象数组
c1.getConstructors();

//返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共(public)构造方法。
c1.getDeclaredConstructor(Class<?>... parameterTypes)

//返回一个 Constructor 对象,该对象反映此 Class 对象所表示的类或接口的指定构造方法。
c1.getConstructor(Class<?>... parameterTypes);

//举例
System.out.println(c1.getConstructor(String.class)); //一个参数,分别为String类型的构造方法
System.out.println(c1.getConstructor(String.class, int.class)); //两个参数,分别为String,int类型的构造方法
System.out.println(c1.getConstructor(null)); //无参构造方法

3.3 Field

1
2
3
4
5
//获取属性
c1.getFields(); //找到public属性,返回一个数组
c1.getDeclaredFields(); //找到所有属性,返回一恶搞数组
c1.getField("name"); //获得指定public属性
c1.getDeclaredField("name"); //获得指定属性

3.4 Method

1
2
3
4
5
6
7
8
c1.getMethods(); // 获取所有public方法(包含父类的方法)
c1.getDeclaredMethods(); // 获取所有方法
c1.getMethod(方法名,参数列表); //获得指定public方法
c1.getDeclaredMethod(方法名,参数列表); //获得指定public方法

// 举例
System.out.println(c1.getMethod("eat", null)); //eat() 参数为null
System.out.println(c1.getMethod("sleep", Integer.class, String.class)); //sleep()方法 参数一个为Integer,一个为String

3.5 Other

1
2
3
 //获取类名
c1.getName(); //包名加类名,org.example...
c1.getSimpleName(); //类的简单名字 Animal

4.创建对象

1
2
3
4
5
6
7
8
// Class.newInstance()创建对象
// 调用无参构造器,如果没有,那么会出现异常
Animal animal1 = (Animal) c1.newInstance();

// Construct.newInstance()创建对象
// 调用construct代表的构造器来创建对象
Constructor constructor = c1.getConstructor(null);
Animal animal2 = (Animal) constructor.newInstance();

加入构造器是private的,那么就不可以直接使用,需要用setAccessible()方法设置权限后在使用

setAccessible()

值为 true 则指示反射的对象(获取的Method,Field,Construct等)在使用时应该取消 Java 语言访问检查。值为 false 则指示反射的对象应该实施 Java 语言访问检查;实际上setAccessible是启用和禁用访问安全检查的开关,并不是为true就能访问为false就不能访问 ;比如原来是public,无论是true或者false都可以访问。

1
2
3
Constructor constructor = c1.getDeclaredConstructor(null);
constructor.setAccessible(true);
Animal animal2 = (Animal) constructor.newInstance();

5.调用方法

使用invoke()来执行

1
2
3
4
5
6
// 参数:
// obj - 从中调用底层方法的对象
// bargs - 用于方法调用的参数
// 返回:
// 使用参数 args 在 obj 上指派该对象所表示方法的结果
public Object invoke(Object obj, Object... args)

注意:

如果底层方法是静态的,那么obj参数为null

如果底层方法所需的形参数为零个,则所提供的 args 数组长度可以为 0 或 null。

如果底层方法是静态的,并且尚未初始化声明此方法的类,则会将其初始化。

如果方法正常完成,则将该方法返回的值返回给调用者;如果该值为基本类型,则首先适当地将其包装在对象中。但是,如果该值的类型为一组基本类型,则数组元素不 被包装在对象中;换句话说,将返回基本类型的数组。如果底层方法返回类型为 void,则该调用返回 null。

三、注解与反射

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
//参数:要获取的注解的类型。
//返回值:此方法返回注解的指定对象。没有则返回null
public T getAnnotation(Class<T> annotationClass)

// 获取包上的注解
Package packagee = Package.getPackage("org.example.reflect");
packagee.getAnnotations()

// 获取类上的注解
Annotation[] annotations = c1.getAnnotations();

// 获取成员属性注解
Field name = c1.getDeclaredField("name");
Annotation[] annotations1 = name.getAnnotations();

//获取构造器上的注解
Constructor constructor = c1.getConstructor(null);
AnyAnnotation[] annotationsByType = constructor.getAnnotationsByType(AnyAnnotation.class);

// 获取构造器参数上的注解
Parameter[] parameters = constructor.getParameters();
for (Parameter parameter : parameters) {
Annotation[] annotations2 = parameter.getAnnotations();
}

// 获取方法上的注解
Method method = clazz.getMethod("method", String.class);
AnyAnnotation annotation = method.getAnnotation(AnyAnnotation.class);

// 获取方法参数上的注解
Parameter[] parameters1 = method.getParameters();
for (Parameter parameter : parameters1) {
printAnnotation(parameter.getAnnotations());
}