一、反射的概念
1.概念
我们知道,一般情况下,要创建一个类的实例对象,可以使用new关键字:
包名.类名 对象名 = new 包名.类名(<构造函数实参>);
但有时,程序处于运行期,而我们不知道已经实例化的对象属于哪个类,也不知道这个类有哪些方法和属性。
反射机制就是在程序运行期时获取程序中对象所属的类与相关信息,并能调用它的方法和属性的一种动态机制。
反射顾名思义,是从实例对象获得类的信息,与实例化对象的步骤恰好相反:
一般 | |
---|---|
反射 |
2.功能
Java反射机制主要提供以下功能:
- 运行时判断任意一个对象所属的类;
- 运行时构造任意一个类的对象;
- 运行时判断任意一个类所具有的成员变量和方法;
- 运行时调用任意一个对象的方法;
- 生成动态代理。
3.优缺点
优点:
● 在运行时动态获取类的实例,提高灵活性和扩展性;
● 与动态编译结合,实现强大的功能;
● 方便地创建灵活的代码,可以在运行时再装配,无需进行源代码地链接。
缺点:
● 反射会消耗一定的系统资源。
● 反射调用方法可以忽略权限检查,破坏类的封装性。
二、Java反射机制API
1.Class类
Class类是反射机制的核心类。Class类的一个实例表示Java中一种数据类型的实体,包括类、接口、枚举、注解、数组、基本数据类型和 void。Class类没有公用的构造方法,实例是JVM在类加载时自动创建的。
每一个类或接口,都有一个静态变量class,另外每一个对象都有一个继承自Object类的getClass()方法,可以获取Class实例:
Class class1 = A.class;
Class class2 = new A().getClass();
//class1和class2均为class test.A
Class类提供了很多方法用于获得类的相关信息,一些用于判断Class实例的性质,一些用于获取Class实例的方法和属性等信息:
Class class1 = A.class;
class1.isPrimitive(); //判断是否是基本类型
class1.getName(); //获取类名称,包括包名和类名
class1.getSuperclass(); //获取父类Class实例
class1.getConstructors(); //获取所有public构造方法
class1.getMethods(); //获取所有public方法
class1.getFields(); //获取所有public成员变量
具体见文档:Java13文档-Class类
2.java.lang.reflect包
java.lang.reflect包中提供了反射机制用到的类,如下:
类名 | 说明 |
---|---|
Constructor | 提供类的构造方法信息 |
Field | 提供类的成员变量信息 |
Method | 提供类的成员方法信息 |
Array | 提供动态创建和访问数组的方法 |
Modifier | 提供类和成员的访问修饰符信息 |
下面将介绍这些类的简单使用,具体的方法和使用见文档:Java13文档-reflect包
Class<?> class1 = A.class;
Method[] methods = class1.getMethods();
for(Method m : methods) {
System.out.print(m.getName() + "\t");
System.out.print(Modifier.toString(m.getModifiers()) + "\n");
}
/*
Output:
wait public final native
wait public final
wait public final
equals public
toString public
hashCode public native
getClass public final native
notify public final native
notifyAll public final native
*/
三、通过反射访问类
为方便讨论,下面创建一个Computer类,用于测试反射访问类的方法。
class Computer {
String typeCPU;
private String ipAdress;
protected int wordLength;
public double batteryCapacity;
private Computer() {
}
public Computer(String typeCPU, String ipAdress, int wordLength, double batteryCapacity) {
this.typeCPU = typeCPU;
this.ipAdress = ipAdress;
this.wordLength = wordLength;
this.batteryCapacity = batteryCapacity;
}
public Computer(String... args) {
typeCPU = args[0];
ipAdress = args[1];
wordLength = Integer.valueOf(args[2]);
batteryCapacity = Double.valueOf(args[3]);
}
void print() {
System.out.printf(
"I'm a computer.\nMy CPU is %s.\nMy ipAddress is %s.\nMy wordLength is %d.\nMy battery capacity is %f.\n",
typeCPU, ipAdress, wordLength, batteryCapacity);
}
protected boolean changeIP(String newIP) {
if(newIP.length() == 0)
return false;
this.ipAdress = newIP;
return true;
}
private void batteryLoss() {
batteryCapacity -= 10;
}
public int getWordLength() {
return wordLength;
}
}
1.访问构造方法
动态访问构造方法首先需要创建一个Constructor对象,方法有:
● getConstructors():获取所有public构造方法
● getConstructor(Class<?>…parameterTypes):根据参数表获取一个public构造方法
● getDeclaredConstructors():获取所有构造方法
● getDeclaredConstructor(Class<?>…parameterTypes):根据参数表获取一个构造方法
在获取Constructor对象后,可以使用提供的方法访问构造方法的信息。
Constructor<?>[] constructors = Computer.class.getDeclaredConstructors();
int i = 1;
for (Constructor<?> c : constructors) {
System.out.println("Constructor" + i++ + ":");
System.out.println("参数是否可变: " + c.isVarArgs()); // isVarArgs() 判断参数是否可变
System.out.print("参数依次为:");
Class<?>[] argsClasses = c.getParameterTypes(); // getParameterTypes() 获取参数表
for (Class<?> clz : argsClasses)
System.out.print(clz.getName() + " ");
System.out.println();
Computer computer = null;
int trytime = 1;
while (computer == null) {
try {
// newInstance(initargs) 使用该构造函数实例化
if(trytime == 1)
computer = (Computer) c.newInstance("Core I7", "0.0.0.0", 64, 100);
else if(trytime == 2)
//可变参数需要嵌套两层传递参数
computer = (Computer) c.newInstance(new Object[] {new String[] {"Core I5", "192.168.1.1", "32", "98" , "15"}});
else
computer = (Computer) c.newInstance();
}
catch (IllegalAccessException e) { //无访问权限,重新设置
System.out.println("无访问权限,重新设置!");
c.setAccessible(true); // 设置函数访问权限,对于private默认为false,必须设置为true才能访问
trytime++;
}
catch (IllegalArgumentException e) { //参数表不对,更换newInstance方法调用
System.out.println("参数表不正确!");
trytime++;
}
catch (Exception e) {
System.out.println(e.getMessage());
}
}
computer.print();
}
/*
Output:
Constructor1:
参数是否可变: true
参数依次为:[Ljava.lang.String;
参数表不正确!
I'm a computer.
My CPU is Core I5.
My ipAddress is 192.168.1.1.
My wordLength is 32.
My battery capacity is 98.
Constructor2:
参数是否可变: false
参数依次为:java.lang.String java.lang.String int double
I'm a computer.
My CPU is Core I7.
My ipAddress is 0.0.0.0.
My wordLength is 64.
My battery capacity is 100.
Constructor3:
参数是否可变: false
参数依次为:
无访问权限,重新设置!
参数表不正确!
I'm a computer.
My CPU is null.
My ipAddress is null.
My wordLength is 0.
My battery capacity is 0.
*/
2.访问方法
动态访问成员方法首先需要创建一个Method对象,方法有:
● getMethods():获取所有public方法
● getMethods(String name,Class<?> …parameterTypes):根据方法名和参数表获取一个public方法
● getDeclaredMethods():获取所有方法
● getDeclaredMethods(String name,Class<?>…parameterTypes):根据方法名和参数表获取一个方法
在获取Method对象后,可以使用提供的方法访问成员方法的信息。
获取参数表等使用的方法与构造方法一致,这里不再演示。
Computer computer = new Computer("Core I7", "192.168.1.101", 64, 98);
Method[] methods = computer.getClass().getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName() + ":");
System.out.printf("方法的返回值类型是%s\n", method.getReturnType().getName()); //getReturnType() 获取返回类型
String methodName = method.getName();
try {
switch (methodName) { //invoke(obj,args) 对obj对象执行该方法,参数表为args
case "changeIP" -> {
method.invoke(computer, "192.168.1.102");
computer.print();
}
case "batteryLoss" -> {
method.setAccessible(true);
method.invoke(computer);
computer.print();
}
case "getWordLength" -> System.out.println("字长为"+method.invoke(computer));
}
} catch (Exception e) {
System.out.println(e.getMessage());
}
System.out.println("----------------------------");
}
/*
Output:
print:
方法的返回值类型是void
----------------------------
getWordLength:
方法的返回值类型是int
字长为64
----------------------------
batteryLoss:
方法的返回值类型是void
I'm a computer.
My CPU is Core I7.
My ipAddress is 192.168.1.101.
My wordLength is 64.
My battery capacity is 88.
----------------------------
changeIP:
方法的返回值类型是boolean
I'm a computer.
My CPU is Core I7.
My ipAddress is 192.168.1.102.
My wordLength is 64.
My battery capacity is 88.
----------------------------
*/
3.访问成员变量
动态访问成员首先需要创建一个Field对象,方法有:
● getFields():获取所有public变量
● getField(String name):根据变量名获取一个public变量
● getDeclaredFields():获取所有变量
● getDeclaredField(String name):根据变量名获取一个变量
在获取Field对象后,可以使用提供的方法访问成员变量的信息。
Computer computer = new Computer("Core I7", "192.168.1.101", 64, 98);
Field[] fields = computer.getClass().getDeclaredFields();
for(Field f : fields) {
System.out.println(f.getName() + ":");
if(f.getName().equals("ipAdress"))
f.setAccessible(true); //设置private变量的访问权限
System.out.println("类型:" + f.getType().getName()); //getType() 获取变量类型
try {
System.out.println("原先值:" + f.get(computer)); //get(obj) 获取obj中f的值
switch (f.getName()) { //set(obj,value) 将obj中f的值修改为value
case "typeCPU" -> f.set(computer, "Core I5");
case "ipAdress" -> f.set(computer, "192.168.1.105");
case "wordLength" -> f.set(computer, 32);
case "batteryCapacity" -> f.set(computer, 80);
}
System.out.println("修改后:" + f.get(computer));
}
catch (Exception e) {
System.out.println(e.getMessage());
}
}
computer.print();
/*
Output:
typeCPU:
类型:java.lang.String
原先值:Core I7
修改后:Core I5
ipAdress:
类型:java.lang.String
原先值:192.168.1.101
修改后:192.168.1.105
wordLength:
类型:int
原先值:64
修改后:32
batteryCapacity:
类型:double
原先值:98.0
修改后:80.0
I'm a computer.
My CPU is Core I5.
My ipAddress is 192.168.1.105.
My wordLength is 32.
My battery capacity is 80.
*/