Java中的反射
Java反射允许程序在运行时动态地获取类的信息,并操作对象。它提供了以下能力:
特点
-- 检查类的结构:包括字段、方法、构造函数等。
-- 创建实例:无需通过new
关键字,可以直接创建对象实例。
-- 调用方法和访问字段:即使这些成员是私有的,也可以通过设置访问权限来操作。
注意事项
-- 反射可能会破坏封装性,因为可以访问和修改通常不可见的(如私有)成员。
-- 使用反射可能会影响性能,因为它绕过了编译时的优化。
类信息来自于哪里?
类信息来源于`.class`文件,这些文件由Java源代码编译生成。JVM加载这些类文件,并将其转换为内存中的`Class`对象。以下是获取`Class`对象的方法
三个途径
-- Class.forName("ClassName")
-- ClassName.class
-- Object.getClass()
第一个Class.forName("")这种方法明确地要求JVM去加载指定名称的类(如果还没有加载的话)。然后获取这个类。
第二个ClassName.class
这种方式是通过类名直接引用类的Class
对象。
第三个Object.getClass()这种是通过已经创建好的对象中去获取类的信息。
注意
- 确保类路径正确,特别是使用`Class.forName()`时。
- 注意处理`ClassNotFoundException`或`NoClassDefFoundError`异常。
获取类信息
比如,现在有一个Student类
示例类
class Student{
private String name;
public int age;
double height;
private char sex;
public Student() {
name = "张三";
age = 18;
height = 1.80;
sex = '男';
}
private Student(String name, int age, double height, char sex) {
this.name = name;
this.age = age;
this.height = height;
this.sex = sex;
}
public Student(char sex){
this.sex = sex;
}
private void run() {
System.out.println("student run");
}
public int getAge() {
return age;
}
String getName() {
return name;
}
protected void run(String a,int b) {
System.out.println("student run"+a+b);
}
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height +
", sex=" + sex +
'}';
}
}
获取这个类
Class clazz = Class.forName("DailyTest._250218.Student");
获取字段
//获取具体的单个字段
Field heightField = clazz.getDeclaredField("height");
System.out.println(heightField);
//获取一个字段数组
Field[] fields = clazz.getFields(); // 获取所有公共字段,包括继承来的
Field[] declaredFields = clazz.getDeclaredFields(); // 获取本类声明的所有字段,不考虑修饰符
System.out.println(Arrays.toString(fields));
System.out.println(Arrays.toString(declaredFields));
getDeclaredField("")里面的内容进行筛选,判断那个字段与其一致。
输出
double DailyTest._250218.Student.height
[public int DailyTest._250218.Student.age]
[private java.lang.String DailyTest._250218.Student.name, public int DailyTest._250218.Student.age, double DailyTest._250218.Student.height, private char DailyTest._250218.Student.sex]
Declared
是否加Declared的区别:`getFields()`只返回公共成员(包括继承的public修饰的字段),而`getDeclaredFields()`返回该类声明的所有成员(忽略访问修饰符)。
获取方法
//获取单个方法
Method runMethod = clazz.getDeclaredMethod("run");
System.out.println(runMethod);
//获取一个方法数组
System.out.println(Arrays.toString(clazz.getMethods()));
System.out.println(Arrays.toString(clazz.getDeclaredMethods()));
getDeclaredMethod("run")run代表那个方法名叫做run,如果有参数,则在后米加上参数的类,这个找的是无参的run,所以没有加类。
比如下面这个
Method run2Method = clazz.getDeclaredMethod("run", String.class, int.class);
System.out.println(run2Method);
输出
private void DailyTest._250218.Student.run()
[public int DailyTest._250218.Student.getAge(), public java.lang.String DailyTest._250218.Student.toString(), public boolean java.lang.Object.equals(java.lang.Object), public native int java.lang.Object.hashCode(), public final native java.lang.Class java.lang.Object.getClass(), public final native void java.lang.Object.notify(), public final native void java.lang.Object.notifyAll(), public final void java.lang.Object.wait(long) throws java.lang.InterruptedException, public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException, public final void java.lang.Object.wait() throws java.lang.InterruptedException]
[public int DailyTest._250218.Student.getAge(), java.lang.String DailyTest._250218.Student.getName(), protected void DailyTest._250218.Student.run(java.lang.String,int), private void DailyTest._250218.Student.run(), public java.lang.String DailyTest._250218.Student.toString()]
protected void DailyTest._250218.Student.run(java.lang.String,int)
Declared
是否加Declared的区别:`getMethods()`只返回公共成员(包括继承来的,比如equals,在这个类中未定义,但是它从Object类中继承而来的),而`getDeclaredMethods()`返回该类声明的所有方法(忽略访问修饰符)。
获取构造函数
//获取单个构造函数,空参代表空参构造
Constructor constructor = clazz.getDeclaredConstructor();
//获取一个构造函数数组
System.out.println(Arrays.toString(clazz.getConstructors()));
System.out.println(Arrays.toString(clazz.getDeclaredConstructors()));
同方法那一部分,如果有参数,则在函数后面传入,参数类型对应的类。
输出
public DailyTest._250218.Student()
[public DailyTest._250218.Student(), public DailyTest._250218.Student(char)]
[public DailyTest._250218.Student(), private DailyTest._250218.Student(java.lang.String,int,double,char), public DailyTest._250218.Student(char)]
Declared
是否加Declared的区别:`getConstructors()`只返回public修饰的构造函数,而getDeclaredConstructors()`返回该类所有的构造函数(忽略访问修饰符)。
对该类的对象进行操作
暴力反射
用于绕过Java语言访问控制检查。对于私有成员变量或方法,可以通过`setAccessible(true)`使其可访问。
方法
(属性/方法/构造器).setAccessible(true);
创建对象
在有了构造器后,调用构造器的newInstance()方法,如果需要参数,则直接在后面加。
示例代码
Constructor constructor = clazz.getDeclaredConstructor();
Student student = (Student) constructor.newInstance();
System.out.println(student);
//暴力反射
Constructor constructor3 = clazz.getDeclaredConstructor(String.class, int.class, double.class, char.class);
constructor3.setAccessible(true);
Student student2 = (Student) constructor3.newInstance("李四", 19, 1.80, '男');
System.out.println(student2);
输出
Student{name='张三', age=18, height=1.8, sex=男}
Student{name='李四', age=19, height=1.8, sex=男}
操作字段
主要就两个函数
Field.get(对象)
Field.get(对象,值)
非常的直观,一个设置值,一个获取值。
只不过和平常用的不太一样的是,字段.set(对象),常见的一般是对象.get()
示例代码
// 先创建一个对象
Student student = new Student();
// 创建字段
Field nameField = clazz.getDeclaredField("name");
Field heightField = clazz.getDeclaredField("height");
// 暴力反射姓名
nameField.setAccessible(true);
// 输出信息,通过反射获取这个类的字段值
System.out.println("姓名:"+nameField.get(student));
System.out.println("身高:"+heightField.get(student));
// 开始对对象进行操作
System.out.println(student);
// 改名
nameField.set(student,"王五");
System.out.println(student);
// 该身高
heightField.set(student,1.70);
System.out.println(student);
输出结果
姓名:张三
身高:1.8
Student{name='张三', age=18, height=1.8, sex=男}
Student{name='王五', age=18, height=1.8, sex=男}
Student{name='王五', age=18, height=1.7, sex=男}
调用方法
方法的调用主要就一个函数
方法.invoke(对象)
示例代码
Student student = new Student();
// 获取run方法
Method run = clazz.getDeclaredMethod("run");
// 暴力映射
run.setAccessible(true);
// 调用方法
run.invoke(student);
// 获取getName方法
Method getNameMethod = clazz.getDeclaredMethod("getName");
// 运行getName方法
String name = (String) getNameMethod.invoke(student);
// 输出结果
System.out.println("name:"+name);
输出结果
student run
name:王五