发布于 ,更新于 

Java 的反射与注解

反射

反射机制与 Class 类

反射机制是程序在运行的中途,动态地获取一个类或者对象的成员与方法,并使用它们的机制。反射是通过 java.lang.Class 类的方法来实现。

Java 中每个对象都是 Class 类的一个对象,可以通过下面方式获取到 Class 对象:

  1. 通过调用对象的 getClass() 方法获得:
1
Class classObj = objectName.getClass();
  1. 通过类的隐含静态对象成员变量 class 获得:
1
Class classObj = ClassName.class;
  1. 通过 Class 类的静态方法 forName() 去查找获得。这里必须加上异常处理,因为有可能找不到对应的类。
1
2
3
4
5
try {
Class classObj = Class.forName("com.xxxx.xxxx.ClassName");
} catch(ClassNotFoundException e) {
e.printStackTrace();
}

获取构造方法

使用 Class 类对象的下面成员方法获取类的构造方法:

方法 返回值 说明
getConstructors() Constructor对象数组 返回类的所有public的构造方法
getDeclaredConstructors() Constructor对象数组 返回类的所有构造方法,按声明顺序返回
getConstructor(Class<?>... paramTypes) Constructor对象 返回类的一个public的指定构造方法
getDeclaredConstructor(Class<?>... paramTypes) Constructor对象 返回类的一个指定构造方法

举例:

1
2
3
4
5
6
7
8
9
10
class ClassName1 {
public ClassName1(String value1, int value2) {
}
}
//----
public class Test1 {
public Test1() {}
Class cls = ClassName1.class;
constructor = cls.getConstructor(String.class, int.class);
}

Constructor 类中的常用方法:

方法 说明
newInstance() 调用构造器创建一个该类的新实例对象
isVarArgs() 该构造方法是否可以带可变数量的参数,是则返回 true,否则返回 false
getParameterTypes() 返回 Class 对象数组,表示该构造方法的参数类型
getExceptionTypes() 返回 Class 对象数组,表示该构造方法可能抛出的异常类型
setAccessible(boolean) 设置为 true 则可以调用非 public 的构造方法来创建对象
getModifiers() 返回一个整数,表示该构造方法的修饰符

修饰符的解释

getModifiers() 方法会返回一个整数代表的修饰符,用Modifier类的下面静态方法可以解释修饰符:

方法 说明
isPublic(int) 是否 public 修饰
isProtected(int) 是否 protected 修饰
isPrivate(int) 是否 private 修饰
isStatic(int) 是否 static 修饰
isFinal(int) 是否 final 修饰
toString(int) 将所有修饰与字符串的形式返回

获取和访问成员变量

通过 Class 类对象的下面方法获取成员变量的变量信息:

方法 说明
getField(String) 返回一个 Field 对象,代表一个 public 的成员
getFields() 返回一个 Field 对象数组,代表所有 public 的成员列表(包括父类的成员)
getDeclaredField(String) 返回一个 Field 对象,代表一个的成员
getDeclaredFields() 返回一个 Field 对象数组,代表所有的成员列表(不包括继承的成员)

Field 类常用的方法有:

方法 说明
get(Object obj) 返回此 Field 表示的成员对象
set(Object obj, Object value) 将此 Field 表示的成员对象设置为指定值
getName() 获取字段的名称
getType() 返回一个 Class 对象,获取字段的类型
getInt(Object obj) 获取 int 类型的 obj 的成员变量的值
setInt(Object obj, int value) 设置 int 类型的 obj 的成员变量的值
getFloat(Object obj) 获取 float 类型的 obj 的成员变量的值
setFloat(Object obj, float value) 设置 float 类型的 obj 的成员变量的值
getBoolean(Object obj) 获取 boolean 类型的 obj 的成员变量的值
setBoolean(Object obj, boolean value) 设置 boolean 类型的 obj 的成员变量的值
setAccessible(boolean) 设置为 true 则可以访问非 public 的成员
getModifiers() 返回一个整数,表示该成员的修饰符

获取和访问方法

通过 Class 类对象的下面方法获取方法信息:

方法 说明
getMethod(String name, Class<?>... paramTypes) 返回一个 Method 对象,代表一个 public 的方法
getMethods() 返回一个 Method 对象数组,代表所有 public 的方法
getDeclaredMethod(String name, Class<?>... paramTypes) 返回一个 Method 对象,代表一个方法
getDeclaredMethods() 返回一个 Method 对象数组,代表所有的方法(不包括继承的方法)

Method 类常用的方法有:

方法 说明
invoke(Object obj, Object... args) 调用 obj 对象的方法
getName() 获取方法名
getReturnType() 返回一个 Class 对象,获取方法的返回值类型
getParameterTypes() 返回 Class 对象数组,获取方法的参数列表的类型的数组
getExceptionTypes() 返回 Class 对象数组,获取方法的可能抛出的类型的数组
isVarArgs() 该方法是否可以带可变数量的参数,是则返回 true,否则返回 false
getModifiers() 返回一个整数,表示该方法的修饰符

注解 (Annotation)

注解是以结构化的方式为程序提供额外信息,包括能被编译器、加载器、解释器等自动处理的额外信息。
可以进行注解的有:

  • 类型(类、接口、枚举)
  • 构造方法
  • 方法
  • 成员变量
  • 参数
  • 局部变量

注解类型 是指一种特殊的接口类型,注解 则是指 注解类型 的一个实例。

注解中的信息是以 键 / 值 对的形式存在的,可以存在 0 个或者多个键值对。没有键值对的注解叫标记注解类型。

标准注解

Java 在 API java.lang 包中定义了三个标准的内置注解。如下:

1. @Override

@Override 是一个标记注解类型,用于编译器检查该方法是否是重载方法,可以方式程序员在重写覆盖某个方法时犯错。如果要重写的方法不是父类的方法,则编译器会报错。

2. @Deprecated

@Deprecated 是一个标记注解类型,表示该字段以后版本可能会被废弃,请使用者不应该继续使用,请采用其他方法代替。编译时会有警告提示。但是目前版本还是被使用。

3. @SuppressWarnings

@SuppressWarnings 注解是请编译器不要提示某些类型的警告信息出现。它的用法是传递一个字符串数组作为参数。例如:

1
2
@SuppressWarnings(value=("deprecation", "unchecked", "unused"))
public void func1(void) {}

元注解

元注解 是对注解进行注解的注解。在 API java.lang.annotation 包中定义了四个标准的元注解。

1. @Documented

@Documented 是一个标记注解类型,被它注解的元素应该被 javadoc 文档化。

2. @Inherited

默认标记在父类上面的注解是不会被子类继承的。@Inherited 表示注解类型会被子类继承。

3. @Retension

表示注解的有效期,也就是注解的保留时间。它的值是一个 java.lang.annotation.RetensionPolicy 的枚举值。该枚举有下面值,表示下面不同的含义:

说明
SOURCE 注解只存在与源文件,编译器解释该注解之后就会丢弃
CLASS 注解会留存在*.class文件中,但是 JVM 不会去读取此注解。默认值
RUNTIME 注解会被 JVM 加载。可以使用反射机制来查询到该注解的内容

4. @Target

用于指明注解修饰的对象范围,也就是指明适用的元素类型。如果未设置 @Target,则表示所有程序元素都适用。 它的值是一个 java.lang.annotation.ElementType 的枚举值。该枚举有下面值,表示下面不同的含义:

说明
PACKAGE 注解只标注包
TYPE 注解只标注类、接口、枚举以及 Annotation 类型
ANNOTATION_TYPE 注解只标注 Annotation 类型
CONSTRUCTOR 注解只标注构造方法
FIELD 注解只标注成员
METHOD 注解只标注方法
PARAMETER 注解只标注参数
LOCAL_VARIABLE 注解只标注局部变量

自定义注解

自定义注解使用 interface 关键字前面加一个 “@” 来实现,自定义注解隐含继承了 java.lang.annotation.Annotation 接口。有些地方需要注意的是:

  • 注解中的方法不需要加上 private、protected、public 等修饰,保持默认即可
  • 注解中的方法可以加上 default 来声明默认值
  • 自定义注解不能继承其他注解或者接口
  • 可以加上元注解来描述注解的使用方式与范围

举例:

1
2
3
4
5
6
7
8
9
10
11
12
package com.testanno;
import java.lang.annotation.*;

@Target(value = {ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Anno1 { // 这里声明一个注解类型叫 Anno1
int value1() default 1;
String value2() default "string-1";
Class type() default int.class;
}

通过反射机制访问注解信息

Constructor、Field、Method 类都是继承于 AccessibleObject 类,提供了一下相关的方法:

  • isAnnotationPresent : 用于查询是否附加了指定类型的注解
  • getAnnotation : 获取指定类型的注解对象
  • getAnnotations : 获取所有的注解对象,返回一个注解对象数组
  • getParameterAnnotations : 获取所有参数所添加的注解对象,返回一个二维注解对象数组

使用注解例子:

1
2
3
4
5
6
7
8
9
10
11
package com.testanno;
public class Test1 { // 直接使用上一个例子中的 Anno1 注解类型
@Anno1(value1 = 100, value2 = "string-2")
public void function1(){
System.out.println("function1");
}
@Anno1
public void function2() {
System.out.println("function2");
}
}

下面例子是通过反射机制来访问注解信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.testanno;

import java.lang.reflect.Method;

public class MyTest {
public static void main(String[] args) {
try {
Class cls = Class.forName("com.testanno.Test1"); // 通过反射获得类对象
Method[] methods = cls.getMethods(); // 获得所有方法
for (Method curMethod: methods) { // 遍历所有方法
if (curMethod.isAnnotationPresent(Anno1.class)) {
Anno1 anno1 = curMethod.getAnnotation(Anno1.class); // 获得方法的注解
System.out.println("Method name = " + curMethod.getName());
System.out.println("value1 = " + anno1.value1() +
", value2 = " + anno1.value2() + "\n");
}
}
}
catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}

输出结果:

1
2
3
4
5
Method name = function2
value1 = 1, value2 = string-1

Method name = function1
value1 = 100, value2 = string-2

完