JavaSE进阶篇【5】
2022年6月30日
第五部分 反射
5.1 Class 类与 Java 反射
Java 的反射 (reflection) 机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为 Java 语言的反射机制。
通过 Java 反射机制,可以在程序中访问已经装载到 JVM 中的 Java 对象的描述,实现访问、检测和修改描述 Java 对象信息本身的功能。在 java.lang.reflection 包中提供了对该功能的支持。
所有 Java 类都继承了 Object 类,在 Object 中定义了一个 getClass() 方法,该方法返回一个类型为 Class 的对象。
通过反射可以访问的主要描述信息如下
组成部分 | 访问方法 | 返回值类型 | 说明 |
---|---|---|---|
包路径 | getPackage() | Package 对象 | 获取该类的存放路径 |
类名称 | getName() | String 对象 | 获得该类的名称 |
继承类 | getSuperclass() | Class 对象 | 获取该类的继承类 |
实现接口 | getInterfaces() | Class 型数组 | 获得该类实现的所有接口 |
构造方法 | getConstructors() | Constructor 型数组 | 获得所有权限为 public 的构造方法 |
方法 | getMethods() | Method 型数组 | 获得所有权限为 public 的方法 |
成员变量 | getFields() | Field 型数组 | 获得所有权限为 public 的成员变量 |
内部类 | getClasses() | Class 型数组 | 获得所有权限为 public 的内部类 |
内部类的声明类 | getDeclaringClass() | Class 对象 | 如果该类为内部类,则返回它的成员类,否则返回 null |
1. 访问构造方法
在通过下列一组方法访问构造方法时,将返回 Constructor 类型的对象或数组。每个 Constructor 对象代表一个构造方法,利用 Constructor 对象可以操纵相应的构造方法。
- getConstructors()
- getConstructor(Class<?>…parameterTypes)
- getDeclaredConstructors()
- getDeclaredConstructor(Class<?>…parameterTypes)
如果是访问指定的构造方法,需要根据该构造方法的入口参数的类型来访问。
Constructor 类中提供的常用方法如下表
方法 | 说明 |
---|---|
isVarArgs() | 查看该构造方法是否允许带有可变数量的参数,如果允许则返回 true,否则返回 false |
getParameterTypes() | 按照声明顺序以 Class 数组的形式获得该构造方法的各个参数类型 |
getExceptionTypes() | 以 Class 数组的形式获得该构造方法可能抛出的异常类型 |
newInstance(Object…initargs) | 通过该构造方法利用指定的参数创建一个该类的对象,如果设置参数则表示采用默认无参数的构造方法 |
setAccessible(boolean flag) | 如果该构造方法的权限为 private,默认为不允许通过反射利用 newInstance(Object…initargs) 方法创建对象。如果先执行该方法,并将入口参数设为 true,则允许创建 |
getModifiers() | 获得可以解析出该构造方法所采用修饰符的整数 |
通过 java.lang.reflect.Modifier 类可以解析出 getModifiers() 方法的返回值所代表的修饰符信息,在该类中提供了一系列用来解析的静态方法,既可以查看是否被指定的修饰符修饰,还可以以字符串的形式获得所有修饰符。该类常用的静态方法如下表所示
静态方法 | 说明 |
---|---|
isPublic(int mod) | 查看是否被 public 修饰符修饰,如果是则返回 true,否则返回 false |
isProtected(int mod) | 查看是否被 protected 修饰符修饰,如果是则返回 true,否则返回 false |
isPrivate(int mod) | 查看是否被 private 修饰符修饰,如果是则返回 true,否则返回 false |
isStatic(int mod) | 查看是否被 static 修饰符修饰,如果是则返回 true,否则返回 false |
isFinal(int mod) | 查看是否被 final 修饰符修饰,如果是则返回 true,否则返回 false |
toString(int mod) | 以字符串的形式返回所有修饰符 |
来看一个访问构造放的例子,首先创建一个 Example_01 类,在该类中声明一个 String 类型成员变量和 3 个 int 型成员变量,并提供 3 个构造方法。
package part5_reflect;
import java.lang.*;
public class Example_01 {
String s;
int i,i2,i3;
private Example_01() {
}
protected Example_01(String s, int i) {
this.s = s;
this.i = i;
}
public Example_01(String...strings) throws NumberFormatException{
if(0<strings.length)
i = Integer.valueOf(strings[0]);
if(1<strings.length)
i2 = Integer.valueOf(strings[1]);
if(2<strings.length)
i3 = Integer.valueOf(strings[2]);
}
public void print() {
System.out.println("s="+s);
System.out.println("i="+i);
System.out.println("i2="+i2);
System.out.println("i3="+i3);
}
}
然后编写测试类 Main_01,在该类中通过反射访问 Example_01 类中的所有构造方法,并将该构造方法是否允许带有可变数量的参数、入口参数类型和可能抛出的异常类型信息输出到控制台。
package part5_reflect;
import java.lang.*;
import java.lang.reflect.Constructor;
public class main_01 {
public static void main(String[] args) {
Example_01 example = new Example_01("10","20","30");
Class<?extends Example_01>exampleC = example.getClass();
//获得所有构造方法
Constructor[] declaredConstructors = exampleC.getDeclaredConstructors();
for(int i=0;i<declaredConstructors.length;i++) { //构造遍历方法
Constructor<?>constructor = declaredConstructors[i];
System.out.println("查看是否允许带可变数量的参数:"+constructor.isVarArgs());
System.out.println("该构造方法的入口参数类型以此为:");
Class[] parameterTypes = constructor.getParameterTypes(); //获取所有参数类型
for(int j=0;j<parameterTypes.length;j++) {
System.out.println(" "+parameterTypes[j]);
}
System.out.println("该构造方法可能抛出的异常类型为:");
//获取所有可能抛出异常信息类型
Class[] exceptionTypes = constructor.getExceptionTypes();
for(int j=0;j<exceptionTypes.length;j++) {
System.out.println(" "+exceptionTypes[j]);
}
Example_01 example2 = null;
while(example2==null) {
try {
if(i==2)
example2=(Example_01)constructor.newInstance();
else if(i==1)
example2=(Example_01)constructor.newInstance("7",5);
else {
Object[] parameters = new Object[] {new String[] {"100","200","300"}};
example2=(Example_01)constructor.newInstance(parameters);
}
}catch(Exception e) {
System.out.println("在创建对象时抛出异常,下面执行 setAccessible() 方法");
constructor.setAccessible(true);
}
}
if(example2!=null) {
example2.print();
System.out.println();
}
}
}
}
控制台输出的结果为
2. 访问成员变量
在通过下列一组方法访问成员变量时,将返回 Field 类型的对象或数组。每个 Field 对象代表一个成员变量,利用 Field 对象可以操纵相应的成员变量。
- getFields()
- getField(String naem)
- getDeclaredFields()
- getDeclaredField(String name)
如果是访问指定的成员变量,可以通过该成员变量的名称来访问。
Field 类中提供的常用方法如下表所示
方法 | 说明 |
---|---|
getName() | 获得该成员变量的名称 |
getType() | 获得表示该成员变量类型的 Class 对象 |
get(Object obj) | 获得指定对象 obj 中成员变量的值,返回值为 Object 型 |
set(Object obj, Object value) | 将指定对象 obj 中成员变量的值设置为 value |
getInt(Object obj) | 获得指定对象 obj 中类型为 int 的成员变量的值 |
setInt(Object obj, int i) | 将指定对象 obj 中类型为 int 的成员变量的值设置为 i |
setAccesible(boolean flag) | 此方法可以设置是否忽略权限限制直接访问 private 等私有权限的成员变量 |
getModifiers() | 获得可以解析出该成员变量所采用修饰符的整数 |
3. 访问方法
在通过下列一组方法访问方法时,将返回 Method 类型的对象或数组。每个 Method 对象代表一个方法,利用 Method 对象可以操纵相应的方法。
- getMethods()
- getMethod(String name,Class<?>…parameterTypes)
- getDeclaredMethods()
- getDeclaredMethod(String name,Class<?>parameterTypes)
如果是访问指定的方法,需要根据该方法的名称和入口参数的类型来访问,例如,访问一个名称为 print、入口参数类型依次为 String 和 int 型的方法,通过下面两种方式均可实现
- objectClass.getDeclaredMethod(“print”, String.class, int.class)
- objectClass.getDeclaredMethod(“print”,new Class[]{String.class, int.class})
Method 类中提供的常用方法如下表所示
方法 | 说明 |
---|---|
getName() | 获得该方法的名称 |
getParameterTypes() | 按照声明顺序以 Class 数组的形式获得该方法的各个参数的类型 |
getReturnType() | 以 Class 对象的形式获得该方法的返回值的类型 |
getExceptionTypes() | 以 Class 数组的形式获得该方法可能抛出的异常类型 |
invoke(Object obj, Object…args) | 利用指定参数 args 执行指定对象 obj 中的该方法,返回值为 Object 型 |
isVarArgs() | 查看该构造方法是否允许带有可变数量的参数,如果允许则返回 true,否则返回 false |
getModifiers() | 获得可以解析出该方法所采用修饰符的整数 |
5.2 使用 Annotation 功能
Java 中提供了 Annotation 功能,该功能可用于类、构造方法、成员变量、方法、参数等的声明中。该功能并不影响程序的运行,但是会对编译器警告等辅助工具产生影响。下面介绍 Annotation 功能的使用方法,
1. 定义 Annotation 类型
在定义 Annotation 类型时,也需要用到用来定义接口的 interface 关键字,但需要在 interface 关键字前加一个 “@” 符号,即定义 Annotation 类型的关键字为 @interface,这个关键字的隐含意思是继承了 java.lang.annotation.Annotation 接口。例如,下面的代码就定义了一个 Annotation 类型:
public @interface NoMemberAnnotation{
}
上面定义的 Annotation 类型 @NoMemberAnnotation 未包含任何成员,这样的 Annotation 类型被称为 mark annotation。下面代码定义了一个只包含一个成员的 Annotation 类型:
public @interface OneMemberAnnotation{
String value();
}
- String:成员类型。可用的成员类型有 String、Class、primitive、enumerated 和 annotation,以及所列类型的数组
- value:成员名称。如果在所定义的 Annotation 类型中只包含一个成员,通常将成员名称命名未 value
下面代码定义了一个包含多个的 Annotation 类型:
public @interface MoreMemberAnnotation{
String descibe();
Class type();
}
在为 Annotation 类型定义成员时,也可以为成员设置默认值,例如:
public @interface DefaultValueAnnotation{
String describe() default "<默认值>";
Class type() default void.class;
}
在定义 Annotation 类型时,还可以通过 Annotation 类型 @Target 来设置 Annotation 类型适用的程序元素种类。如果未设置 @Target,则表示适用于所有程序元素。枚举类 ElementType 中的枚举常用来设置 @Targer,如下表所示:
枚举常量 | 说明 |
---|---|
ANNOTATION_TYPE | 表示用于 Annotation 类型 |
TYPE | 表示用于类、接口和枚举,以及 Annotation 类型 |
CNSTRUCTOR | 表示用于构造方法 |
FIELD | 表示用于成员变量和枚举变量 |
METHOD | 表示用于方法 |
PARAMETER | 表示用于参数 |
LOCAL_VARIABLE | 表示用于局部变量 |
PACKAGE | 表示用于包 |
通过 Annotation 类型 @Retention 可用设置 Annotation 的有效范围。枚举类 RetentionPolicy 中的枚举常量用来设置 @Retention,如下表所示。如果没有设置 @Retention,Annotation 的有效范围为枚举常量 CLASS 表示的范围。
枚举常量 | 说明 |
---|---|
SOURCE | 表示不编译 Annotation 到类文件中,有效范围最小 |
CLASS | 表示编译 Annotation 到类文件中,但是在运行时不加载 Annotation 到 JVM 中 |
RUNTIME | 表示在运行时加载 Annotation 到 JVM 中,有效范围最大 |
2. 访问 Annotation 信息
如果在定义 Annotation 类型时将 @Retention 设置为 RetentionPolicy.RUNTIME,那么在运行程序时通过反射就可以获取到相关的 Annotation 信息,如获取构造方法、字段和方法的 Annotation 信息。
类 Constructor、Field 和 Method 均继承了 AccessibleObject 类,在 AccessibleObject 类中定义了 3 个关于 Annotation 的方法:
- isAnnotationPresent(Class<?extends Annotation>annotationClass) 方法:用来查看是否添加了指定类型的 Annotation,如果是则返回 true,否则返回 false
- getAnnotation(ClassannotationClass) 方法:用来获得指定类型的 Annotation,如果存在则返回相应的对象,否则返回 null
- getAnnotations() 方法:用来获取所有的 Annotation,该方法将返回一个 Annotation 数组
在类 Constructor 和 Method 中还定义了方法 getParameterAnnotation(),用来获得所有参数添加的 Annotation,将以 Annotation 类型的二维数组返回,在数组中的顺序与声明的顺序相同,如果没有参数则返回一个长度为 0 的数组;如果存在未添加 Annotation 的参数,将用一个长度为 0 的嵌套数组占位。