1.反射
顾名思义
反射允许对成员变量,成员方法和构造方法的信息进行编程访问
获取class对象
获取字段(成员变量)-》获取修饰符-》获取名字-》获取类型-》获取赋值/获取值
获取构造方法-》获取修饰符-》获取名字-》获取形参-》创建对象
获取成员方法。-》获取修饰符-》获取名字-》获取形参-》获取返回值-》抛出的异常-》获取注解-》运行方法
2.获取Class对象的三种方式
1.Class.forName("全类名");
2.类名.class
3.对象.getClass();
.java-.class(源代码阶段)-》是这个1.Class.forName("全类名");
A.class-》 内存加载阶段-》是这个2.类名.class
A a=new A();运行阶段-》是这个3.对象.getClass();
首先写好javaben配置环境
然后封装一下student类
public class student {private String name;private int age ;public student(){}public student(String name) {this.name = name;}protected student(int age){this.age = age;}private student(String name,int age){this.name =name;this.age = age;}/*** 获取* @return name*/public String getName(){return name;}/***** @param name*/public void setName(String name) {this.name = name;}/***** @return age*/public int getAge() {return age;}
}
第一种方式
特点:最为常用的
全类名 :包名加类名
第二种方式
特点:一般更多的是当做参数进行传递
照样可以输出student类
第三种方式
特点:当我们已经有了这个类的对象是,才可以使用
3.反射获取Constructor构造方法
1.前言
在Java当中,万物皆对象
class中用于获取构造方法的方法
1.返回所有公共构造方法对象的数组
Constructor<?>[] getConstructors()
2.返回所有构造方法对象的数组
Constructor<?>[] getDeclaredConstructors()
3.返回单个公共构造方法对象
Constructor<T> getConstructor(Class<?>... parameterTypes)
4.返回单个构造方法对象
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
constructor类中用于创建对象的方法
newInstance(Object... initargs)
根据指定的构造方法创建对象
setAccessible(boolean flag)
设置为true,表示取消访问控制
2.获取公共(public)构造方法
1.获取class字节码文件对象
Class<?> clazz = Class.reflect("com.itheima.myreflect2.Student");
2.获取构造方法
Constructor[] cons = clazz.getConstructors();
for (Constructor con : cons){System.out.println(con);}
控制台打印
public student() //反射空参数
public student(java.lang.String)//反射字符串
可以发现都获取到了public的构造方法
3.获取所有(all)构造方法
Class clazz =Class.forName("student");Constructor[] cons2 = clazz.getDeclaredConstructors();for (Constructor con : cons2) {System.out.println(cons2);}
运行,发现四个构造方法全部被反射到了
进行对比,发现确实是这样
不指定类型,获取空参构造
Constructor con1 = clazz.getDeclaredConstructor();System.out.println(con1);
指定类型,获取指定类型构造,当然getDeclaredConstructor方法是获取所有构造方法的,所有这里能够获取
到public方法
Constructor con2 = clazz.getDeclaredConstructor(String.class);System.out.println(con2);
如果尝试不用getDeclaredConstructor方法,我们来试一试获取一下Protected属性
Constructor con3 = clazz.getConstructor(int.class);System.out.println(con3);
可以看到程序报错了,在33行
我们使用getDeclaredConstructor()方法,发现就可以成功获取了
获取指定的两个参数
Constructor con4 = clazz.getDeclaredConstructor(String.class,int.class);System.out.println(con4);
既然能够反射出对象的修饰符和类型,那么就能用一些其他方法来进行修饰
这样就能以整数的形式打印出
int modifiers = con4.getModifiers();
System.out.println(modifiers);
还可以获取参数,参数个数,参数类型
我们创建一个参数数组,将其打印出来
Parameter[] parameters = con4.getParameters();for (Parameter parameter : parameters){System.out.println(parameter);
student stu =(student) con4.newInstance("张三", 20);System.out.println(stu);
发现报错了,原来是修饰符是Private
发现getDeclaredConstructor()方法只能查看方法的权限,达不到修改方法的权限
在上面添加一个
setAccessible(true);//临时取消权限的校验
然后发现程序是不报错了,但是应该是版本兼容的问题导致不能显示出来
这种方式也称作暴力反射
4.反射获取字段(成员变量)Field
1.前言
class类中用于获取成员变量的方法
Field[] getFields()
-》返回所有公共成员变量对象的数组
Field[] getDeclaredFields()
-》返回所有成员变量对象的数组
Field getField(String name)
-》返回单个公共成员变量对象
Field getDeclaredField(String name)
-》返回单个成员变量对象
filed类中用于创建对象的方法
void set(Object obj, Object value-
-》赋值
Object get(Object obj)
-》获取值
2.反射获取所有公共变量
Class clazz =Class.forName("Student");//获取class字节码文件的对象Field[] fields = clazz.getFields();//获取公共成员变量
尝试用数组进行打印
for (Field field : fields){System.out.println(field);}
发现只包含了gender
查看student发现确实这样
3.反射获取所有变量
将getFields改为getDeclaredFields
Class clazz =Class.forName("Student");//获取class字节码文件的对象Field[] fields = clazz.getDeclaredFields();//获取所有成员变量for (Field field : fields){System.out.println(field);}
打印输出,发现全部都输出来了
4.反射获取单个成员变量
下列语句
Field gender = clazz.getField("gender");System.out.println(gender);
打印结果
尝试获取name
发现代码报错了,因为name是私有的,这里的方法获取不到,没有这个权限
改一下
Field gender = clazz.getDeclaredField("name");System.out.println(gender);
5.反射获取成员变量的修饰符
Field name = clazz.getDeclaredField("name");System.out.println(name);// 获取权限修饰符int modifiers = name.getModifiers();System.out.println(modifiers);
发现这是私有的2
String n = name.getName();System.out.println(n);
获取成员变量名
获取成员变量的数据类型
Class<?> type = name.getType();System.out.println(type);
获取成员变量记录的值
Student s = new Student("zhangsan",23,"男");name.setAccessible(true);Object value = name.get(s);System.out.println(value);
运行
修改对象里面记录的值
name.set(s,"lisi");System.out.println(s);
5.反射获取成员方法Method
1.前言
Class 类中用于获取成员方法的方法
Method[] getMethods()
-》返回所有公共成员方法对象的数组,包括继承的
Method[] getDeclaredMethods()
-》返回所有成员方法对象的数组,不包括继承的
Method getMethod(String name, Class<?>... parameterTypes)
-》返回单个公共成员方法对象
Method getDeclaredMethod(String name, Class<?>... parameterTypes)
-》返回单个成员方法对象
Method 类中用于创建对象的方法
示例代码
public class Student {private int age;private String name;public Student() {}public Student(String name, int age, String gender) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public void sleep(){System.out.println("睡觉");}private void eat(String something){System.out.println("在吃"+something);}public String toString() {return "Student{name='" + name + "', age=" + age + '}';}
}
1.获取class字节码文件对象
Class clazz =Class.forName("Student");//获取class字节码文件的对象
2.获取里面所有的方法对象
Method[] methods = clazz.getMethods();for (Method method : methods) {System.out.println(method);
打印输出,可以看到很多方法,不过都是public修饰的,但是很多方法都没有我定义的
1.然后发现里面的方法其中也包括父类中包含的公共方法
2.不能获取父类的,但可以获取本类中私有的方法
改为DeclareMethods,可以看到方法少了很多
Method[] methods = clazz.getDeclaredMethods();for (Method method : methods) {System.out.println(method);
3.获取单个(指定)的成员方法
然后发现报错
发现这里的方法是private的,私有的,所以我们需要用获取所有成员方法
//获取指定的单一方法
Method m = clazz.getDeclaredMethod("eat", String.class);System.out.println(m);
4.获取方法的修饰符
int modifiers = m.getModifiers();System.out.println(modifiers);
打印结果发现是2,那么就证明是私有
5.获取方法的名字
String name = m.getName();System.out.println(name);
6.获取方法的形参
可以发现种类很多
Parameter[] parameters = m.getParameters();for (Parameter parameter : parameters) {System.out.println(parameter);
可以发现是string类型的
7.获取方法抛出的异常
在eat这里我设置抛出了IO和NULL两个异常
Class[] exceptionTypes = m.getExceptionTypes();for (Class exceptionType : exceptionTypes) {System.out.println(exceptionType);
发现成功抛出两个设置的异常
8.方法运行
其中s表示方法的调用者。参数“汉堡包”表示在调用方法的时候传递的实际参数
Student s = new Student();
m.setAccessible(true);
m.invoke(s,"汉堡包");
9.方法返回值
Student s = new Student();m.setAccessible(true);Object result=m.invoke(s,"汉堡包");System.out.println(result);
运行发现
6.反射综合练习
1.前言
反射的作用
2.练习
目标要求
私有的学生类
public class Student {private String name;private int age;private char gender;private double height;private String hobby;public Student(String name, int age, char gender, double height, String hobby) {this.name = name;this.age = age;this.gender = gender;this.height = height;this.hobby = hobby;}// Getters for all fieldspublic String getName() { return name; }public int getAge() { return age; }public char getGender() { return gender; }public double getHeight() { return height; }public String getHobby() { return hobby; }
}
私有的老师类
public class Teacher {private String name;private double salary;public Teacher(String name, double salary) {this.name = name;this.salary = salary;}// Getters for all fieldspublic String getName() { return name; }public double getSalary() { return salary; }
}
通过反射可以做到增删改查
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;public class ReflectionExample {public static void main(String[] args) throws Exception {// 创建 Student 对象并打印属性值Object studentObj = createAndPrintObject(Student.class, "小A", 23, '女', 167.5, "睡觉");// 创建 Teacher 对象并打印属性值Object teacherObj = createAndPrintObject(Teacher.class, "播妞", 10000.0);}public static Object createAndPrintObject(Class<?> clazz, Object... params) throws Exception {// 确定参数类型Class<?>[] paramTypes = new Class<?>[params.length];for (int i = 0; i < params.length; i++) {if (params[i] instanceof Integer) {paramTypes[i] = int.class;} else if (params[i] instanceof Character) {paramTypes[i] = char.class;} else if (params[i] instanceof Double) {paramTypes[i] = double.class;} else {paramTypes[i] = params[i].getClass();}}// 获取构造方法并创建对象Constructor<?> constructor = clazz.getConstructor(paramTypes);Object obj = constructor.newInstance(params);// 打印字段printFields(obj);return obj;}public static void printFields(Object obj) throws Exception {Field[] fields = obj.getClass().getDeclaredFields();for (Field field : fields) {field.setAccessible(true);System.out.println(field.getName() + "=" + field.get(obj));}}
}
7.总结
反射这个java机制为啥不安全???
1. 破坏封装性
Java 的核心设计原则之一是封装性(Encapsulation),即类的内部实现细节应该对外隐藏。通过反射,可以访问类的私有字段和方法,绕过访问控制检查。
示例:
class Person {private String name;
}// 使用反射访问私有字段
Field field = Person.class.getDeclaredField("name");
field.setAccessible(true); // 绕过访问限制
Person p = new Person();
field.set(p, "Alice"); // 修改私有字段
影响:
- 破坏了类的设计意图。
- 可能导致对象状态被非法修改,引发不可预测的行为。
2. 绕过访问控制检查
Java 语言本身具有访问修饰符(如 private
、protected
、public
)来控制代码的可访问性。但反射可以通过 setAccessible(true)
直接绕过这些限制。
示例:
Method method = MyClass.class.getDeclaredMethod("secretMethod");
method.setAccessible(true);
method.invoke(obj);
影响:
- 恶意代码可以调用本应受保护的方法。
- 安全敏感操作可能被非法执行。
3. 性能开销大
反射调用方法或访问字段比直接调用慢很多,因为 JVM 需要进行额外的安全检查和动态解析。
性能对比(大致):
操作 | 耗时 |
普通方法调用 | 1 ns |
反射调用 | 100 - 500 ns |
虽然现代 JVM 已经对反射进行了优化,但在高频调用场景下仍然存在显著性能损耗。
4. 破坏模块系统(Module System)
从 Java 9 开始引入了模块系统(JPMS),旨在限制外部代码访问 JDK 内部 API。然而,反射仍然可以通过 --add-opens
或 --add-exports
参数绕过这些限制。
示例:
java --add-opens java.base/java.lang=ALL-UNNAMED ...
影响:
- 导致 JDK 内部实现暴露给应用程序。
- 增加了维护和兼容性风险。
5. 可能导致安全漏洞
如果应用程序使用反射加载并执行任意类中的方法,可能会成为攻击入口。例如:
- 动态加载恶意类。
- 执行非预期的私有方法。
- 修改关键数据结构。
特别是在 Web 应用、插件系统、远程调用等场景中,反射如果不加以限制,容易成为攻击目标。
6. 编译期无法检测错误
反射操作是在运行时进行的,因此许多错误(如方法名拼写错误、参数类型不匹配)只有在运行时才会暴露出来。
示例:
Method method = MyClass.class.getMethod("nonExistentMethod"); // 编译通过
Object result = method.invoke(obj); // 运行时报错
影响:
- 增加调试难度。
- 降低代码健壮性。
安全问题 | 描述 |
破坏封装 | 可以访问私有成员,绕过访问控制 |
安全漏洞 | 恶意代码可通过反射执行危险操作 |
性能问题 | 反射调用效率低,影响性能 |
编译无报错 | 错误只能在运行时发现 |
模块限制失效 | 可绕过 JPMS 的模块隔离机制 |