目录
1、概念
Java的反射(reflection)机制是在运行状态中,它允许在运行时检查和操作类、对象、方法和属性等程序结构。通过反射,可以获取程序在运行时的信息,并能够在程序运行时动态地操作对象;
这种动态获取信息以及动态调用对象方法的功能称为java语言的反射(reflection)机制。在框架中才会大量的使用反射;
静态调用
- 静态调用指的是在编译时就已经确定了方法的调用目标。在静态调用中,编译器会在编译期间就确定方法的签名和调用目标,并且会进行一些优化操作,例如内联、常量折叠等。
- 静态调用的优点是速度快、效率高,缺点是不够灵活,不能在运行时根据情况动态地选择方法的调用目标。
Animal animal1 = new Dog();
Animal animal2 = new Cat();
animal1.makeSound();
animal2.makeSound();
动态调用
- 动态调用指的是在运行时才能确定方法的调用目标。在动态调用中,编译器无法确定方法的签名和调用目标,而是需要在运行时根据实际情况进行选择。
- 动态调用的优点是灵活、适应性强,缺点是速度较慢、效率低。
Animal animal1 = new Dog();
Animal animal2 = new Cat();
//获取类型 获取方法
Method method = animal1.getClass().getMethod("makeSound");
//调用
method.invoke(animal1);
//获取类型 获取方法
method = animal2.getClass().getMethod("makeSound");
//调用
method.invoke(animal2)
2、用途
- 获取类的构造函数、方法和属性
- 实例化对象
- 调用方法
- 操作属性
- 动态创建类
- 动态代理
- 调用私有方法和属性
- 获取泛型信息
注意:反射通常比直接调用类的方法和属性要慢,并且不够安全,因此应该谨慎使用。
Java 运行过程
java 运行过程分为三个阶段:编译阶段、类加载阶段和执行阶段
1. 编译阶段(Java源代码被编译器编译成字节码文件.class文件)
- Java源代码被编译器编译成字节码文件,即后缀为.class的文件
- 编译器将Java源代码转换为JVM可以识别的字节码指令,同时检查语法和语义错误。
2. 类加载阶段(JVM将字节码文件加载到内存中,并将其转换为Java对象)
- 在类加载阶段,JVM将字节码文件加载到内存中,并将其转换为Java对象。JVM会执行以下步骤:
- 加载:通过类的全限定名找到字节码文件,并将其加载到内存中;
- 验证:对字节码文件进行验证,确保其符合Java虚拟机规范;
- 准备:为类的静态变量分配内存,并设置默认值;
- 解析:将类、接口、字段和方法的符号引用解析为直接引用;
- 初始化:执行类构造器<clinit>()方法,给类的静态变量赋初值。
3. 执行阶段
- VM执行字节码文件中的指令,实现Java程序的功能。
Java程序的运行过程可以概括为:编写Java源代码 -> 编译 -> 类加载 -> 执行。
反射主要发挥作用的是在类加载阶段和执行阶段。
- 在类加载阶段,我们可以使用反射API获取类的信息,如类名、方法名、字段名、构造函数等;
- 在执行阶段,我们可以使用反射API动态地创建对象、调用方法、访问字段等。
3、反射相关的类
类名 | 用途 |
Class类 | 代表类的实体,在运行时的java应用程序中表示类的接口 |
Field类 | 代表类的成员变量/属性 |
Method类 | 代表类的方法 |
Constructor类 | 代表累的构造方法 |
Java反射机制可以访问类的私有成员(字段、方法、构造函数等),但是需要注意的是,这种访问方式不够安全,因为它可以绕过类的访问控制机制。
要反射一个私有成员,需要使用以下步骤:
1. 获取 Class 对象
- 需要使用 Class.forName() 方法或类的 .class 语法来获取 Class 对象。
2. 获取 Field、Method 或 Constructor 对象
- 使用 Class 类中的 getDeclaredField、getDeclaredMethod、getDeclaredConstructor 等方法来获取私有成员的 Field、Method 或 Constructor 对象。
3. 设置可访问性
- 由于私有成员默认是不可访问的,因此需要设置它们的可访问性。可以使用 Field、Method 或 Constructor 对象的 setAccessible(true) 方法来设置可访问性。
4. 访问私有成员
- 一旦设置了可访问性,就可以使用 Field、Method 或 Constructor 对象的 get、set、invoke 等方法来访问私有成员。
Class 类(反射机制的起源)
代表类的实体,在运行时的java应用程序中表示类的接口
编译时期生成 .class文件,在类加载阶段,JVM此时就要去解读.class文件 ,被编译后的Java文件. class 也被JVM解析为一个对象,这个对象就是 java.lang.Class ;
这样当程序在运行时,每个java文件就最终变成了Class类对象的一个实例。
我们通过Java的反射机制应用到这个实例,就可以去获得甚至去添加改变这个类的属性和动作,使得这个类成为一个动态的类 .
1. 常用获得类相关的方法
方法 | 功能 |
getClassLoader() | 获得类的加载器 |
getDeclaredClasses() | 返回一个数组,数组中包含该类中所有类和接口类的对象(包括私有的) |
forName(String className) | 根据类名返回类的对象 |
newInstance() | 创建类的实例 |
getName() | 获得类的完整路径名字 |
2. 获得类中构造器相关的方法
方法 | 功能 |
getConstructor(Class...<?> parameterTypes) | 获得该类中与参数类型匹配的公有构造方法 |
getConstructors() | 获得该类的所有公有构造方法 |
getDeclaredConstructor(Class...<?> parameterTypes) | 获得该类中与参数类型匹配的构造方法 |
getDeclaredConstructors() | 获得该类所有构造方法 |
3. 常用获得类中属性相关的方法
方法 | 功能 |
getField(String name) | 获得某个公有的属性对象 |
getFields() | 获得所有公有的属性对象 |
getDeclaredField(String name) | 获得某个属性对象 |
getDeclaredFields() | 获得所有属性对象 |
4. 获得类中方法相关的方法
方法 | 功能 |
getMethod(String name, Class...<?> parameterTypes) | 获得该类某个公有的方法 |
getMethods() | 获得该类所有公有的方法 |
getDeclaredMethod(String name, Class...<?> parameterTypes) | 获得该类某个方法 |
getDeclaredMethods() | 获得该类所有方法 |
4、反射的使用
1. 定义一个学生类
package Reflect;
class Student {
//私有属性name
private String name = "bit";
//公有属性age
public int age = 18;
//不带参数的构造方法
public Student() {
System.out.println("Student()");
}
private Student(String name, int age) {
this.name = name;
this.age = age;
System.out.println("Student(String,name)");
}
private void eat() {
System.out.println("i am eat");
}
public void sleep() {
System.out.println("i am pig");
}
private void function(String str) {
System.out.println(str);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
2. 获取 Class对象的三种方式
1. 通过 Class.forName("类的全路径") 静态方法 获取;前提知道类的全路径
- 可能会抛出 ClassNotFoundException 异常
2. 通过 类名.class 获得,最安全可靠,程序性能高
- 说明任何一个类都有一个隐含的静态成员变量 class
- 仅适合在编译前就已经明确要操作的 Class
3. getClass 方法
public static void main(String[] args) throws ClassNotFoundException {
//1、通过 Class.forName("类的全路径") 静态方法获取
//可能会抛出 ClassNotFoundException 异常
Class<?> c1 = Class.forName("Reflect.Student");
//2、通过 类名.class 获得,最安全可靠,程序性能高
//说明任何一个类都有一个隐含的静态成员变量 class
Class<?> c2 = Student.class;
//3、getClass 方法
Student student = new Student();
Class<?> c3 = student.getClass();
//一个 JVM 中只有一个Class 实例
System.out.println(c1 == c2); //true
System.out.println(c1 == c3); //true
System.out.println(c2 == c2); //true
}
Class<?> 表示一个未知的泛型类型 Class,其中的 <?> 是一个通配符,表示这个泛型类型可以接受任何类型参数。
3. 创建对象
- 用 Class.forName() 方法获取对象
- 调用 Class 类的 newInstance() 或 Constructor 类的 newInstance() 方法 创建对象
- 这些方法返回一个 Object 类型的对象,因为在编译时无法确定具体的类类型。因此,需要在运行时根据需要将其转换为实际的类型。
public static void main(String[] args) {
//1.创建对象
try {
//获取对象
Class<?> c1 = Class.forName("Reflect.Student");
//用Object 接受
Object objectStudent = c1.newInstance();
//强制类型转换
Student student = (Student)objectStudent;
System.out.println("学生对象:"+student);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
注意:
- 如果创建的对象类型与所需的类型不兼容,则会在运行时抛出 ClassCastException 异常。
- 因此,应该使用 instanceof 运算符检查对象的类型,以确保类型转换的安全性
//判断
Student student = null;
if (objectStudent instanceof Student) {
//强制类型转换
student = (Student) objectStudent;
}
System.out.println("学生对象:" + student);
4. 反射私有构造器方法
Java 9 中引入了新的方法,如 Class.getDeclaredConstructor(),它们允许在创建对象时指定参数类型,从而避免了需要在接收对象时进行类型转换的麻烦。
反射私有构造器方法 ,屏蔽内容为获得公有的构造方法
public static void reflectPrivateConstructor() {
//2.反射私有的构造方法 屏蔽内容为获得公有的构造方法
try {
//获取对象
Class<?> c1 = Class.forName("Reflect.Student");
//创建对象时使用了这个构造函数
Constructor<?> constructor = c1.getDeclaredConstructor(String.class,int.class);
//修改访问权限
constructor.setAccessible(true);
//创建
Student student = (Student) constructor.newInstance("hang",18);
System.out.println("学生对象:" + student);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
5. 反射私有属性
- 获取对象
- 获取属性 classTest.getDeclaredField("属性名称");
- 修改访问权限 .setAccessible(true);
- 创建对象
- 设置属性值 filed.set(属性,值) —— SET()
public static void reflectPrivateField(){
//3.反射私有属性
try {
//获取对象
Class<?> classTest = Class.forName("Reflect.Student");
//获取指定属性
Field field = classTest.getDeclaredField("name");
//修改访问权限
field.setAccessible(true);
//创建对象
Student student = (Student) classTest.newInstance();
//设置 属性
field.set(student,"hang");
System.out.println("学生对象:" + student);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
6. 反射私有方法
- 获取对象
- 获取方法 classTest.getDeclaredMethod("方法名", 参数类型.class);
- 修改访问权限 .setAccessible(true);
- 创建对象
- 传参数 method.invoke(属性,参数) —— invoke()
public static void reflectPrivateMethod(){
//4.反射私有方法
try {
//获取对象
Class<?> classTest = Class.forName("Reflect.Student");
//获取指定方法
Method method = classTest.getDeclaredMethod("function", String.class);
//修改访问权限
method.setAccessible(true);
//创建对象
Student student = (Student) classTest.newInstance();
//设置 属性
method.invoke(student,"私有方法访问成功");
System.out.println("学生对象:" + student);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
5、反射的优缺点
反射是Java语言中一项重要的特性,它提供了一种在运行时动态获取、操作类和对象的能力。反射的优点和缺点如下:
优点
- 动态性:反射允许程序在运行时动态地加载、使用和创建对象,而不需要在编译期间就确定对象的类型和方法。
- 适应性:反射可以让程序在运行时根据不同的情况动态地选择、调用不同的方法,适应不同的需求
- 灵活性:反射可以让程序以通用的方式处理不同类型的对象,而不需要针对每种类型都编写专门的代码。
- 扩展性:反射可以扩展现有代码的功能,例如在不修改原有代码的情况下为一个类增加新的方法,通过动态代理来完成
增加程序的灵活性和扩展性,降低耦合性,提高自适应能力;
缺点
- 性能问题:反射的性能比直接调用方法或创建对象要慢,因为反射涉及到动态查找和解析类信息等操作,而这些操作需要消耗一定的时间和资源。
- 安全问题:反射可以访问和修改对象的私有成员,这可能导致安全问题。因此,在使用反射时需要小心谨慎,确保程序的安全性。
- 可读性问题:使用反射可以使程序变得复杂、难以理解和维护,因为反射操作常常是在运行时动态发生的,而不是在代码中静态地声明。
- 代码健壮性问题:由于反射可以访问和修改对象的私有成员,可能会破坏程序的封装性和健壮性,导致程序出现意料之外的错误。