一、定义:
Java 的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为 Java 语言的反射机制。反射被视为动态语言的关键。
通常情况下,我们想调用一个类内部的属性或方法,需要先实例化这个类,然后通过对象去调用类内部的属性和方法;通过 Java 的反射机制,我们就可以在程序的运行状态中,动态获取类的信息,注入类内部的属性和方法,完成对象的实例化等操作。
图中解释了两个问题:
1、程序运行状态中指的是什么时刻:Hello.java 源代码文件经过编译得到 Hello.class 字节码文件,想要运行这个程序,就要通过 JVM 的 ClassLoader (类加载器)加载 Hello.class,然后 JVM 来运行 Hello.class,程序的运行期间指的就是此刻;
2、什么是反射,它有哪些功能:在程序运行期间,可以动态获得 Hello 类中的属性和方法、动态完成 Hello 类的对象实例化等操作,这个功能就称为反射。
二、优点
框架:反射、泛型、注解
1、扩展性和灵活性:通过反射机制,可以实现动态加载类、调用方法、访问字段等操作,从而使程序具有更强的扩展性和灵活性。这在开发框架、插件系统或通用库时非常有用。
2、框架设计:许多 Java 框架(如Spring框架)都广泛使用了反射机制,通过反射可以实现依赖注入、AOP编程等功能。
3、框架源码阅读:许多开源框架和库中广泛使用了反射,学习反射可以帮助更好地理解和阅读这些框架的源码。
4、编写通用工具类:通过反射可以编写一些通用的工具类,比如序列化/反序列化工具、注解处理器、配置文件解析器等。虽然反射机制有很多优点,但也需要注意反射可能带来的性能开销和安全隐患。因此,在使用反射时需要谨慎考虑,并避免滥用。总的来说,学习反射机制有助于提高 Java 程序员的编程技能和应用开发能力。
三、反射的缺点
尽管反射机制提供了很大的灵活性和扩展性,但也存在一些缺点和不足之处,包括:
1、性能开销:使用反射会导致性能开销较大,因为在运行时需要动态地解析类结构、查找方法或字段等,比直接调用方法要慢很多。这对性能要求高的应用可能不太适用。
2、编译时检查失效:由于反射是在运行时进行的,因此编译器无法对反射代码进行类型检查,容易引入类型错误,造成运行时异常。
3、安全性问题:通过反射可以访问和修改类的私有字段和方法,这可能会绕过类的封装性,造成安全隐患,特别是在安全敏感的应用中需要格外小心。4、可读性和维护性差:使用反射的代码通常比直接调用更难理解和维护,因为它隐藏了类的结构和行为,使得代码更加晦涩难懂。
5、性能优化困难:由于反射是在运行时动态进行的,因此很难进行静态分析和优化,使得程序的性能优化变得更加困难。
6、不利于代码重构:使用反射的代码通常依赖于类的具体实现细节,一旦类结构发生变化,可能导致反射代码出错,不利于代码的重构和演化。
综上所述,虽然反射机制为 Java 程序员提供了很多便利,但在实际应用中需要谨慎使用,权衡利弊。在性能要求高、安全性要求严格或代码可维护性重要的情况下,建议尽量避免过度依赖反射机制,寻找其他更合适的解决方案。
类比学习一下:面向对象抽象过程
结构里面包含哪些东西呢: Field、Constructor、Method,同样这些众多的属性、构造函数、方法也有对应的类类型表示他们。
Java.lang.Class;
Java.lang.reflect.Field;
Java.lang.reflect.Method;
Java.lang.reflect.Constructor;
对于类型的学习我们可以参考做月饼的模子,什么样模子就可以做出什么大小,图案的月饼。对于Java里面的int类型是四个字节,那么这个模子就是一个只能存放四个字节的模子,用这个模子做出来的就是int类型。
同理Class、Field、Method、Constructor就分别是类、属性、方法、构造函数的模子。
通过Class可以获得类的所有属性Field[]、方法Method[]、构造方法Constructor[]信息。
通过Field可以获得属性的名字、类型、修饰符
通过Method可以获得方法的名字、参数、返回值。
五、反射常用类
学习反射就需要了解反射相关的一些类,下面我们来看一下如下这几个类:
1、Class:Class 类的实例表示正在运行的 Java 应用程序中的类和接口;
2、Constructor:关于类的单个构造方法的信息以及对它的权限访问;
3、Field:Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限;
4、Method:Method 提供关于类或接口上单独某个方法的信息。
通俗理解,要做反射首先获得字节码对象(描述对应的信息),拿到对象之后才能拿到constructor、method、field等信息,所以第一步首先是获得字节码对象,字节码对象有三种获取方式:
//a.通过类路径获得 Class.forName(类路径)
Class a = Class.forName("com.situ.day23.Student);
//b.类型.class
Class b = Student.class;
//c. 对象.getClass()
Student student = new Student();
Class c = student.getClass();
public void testConstructor() {
Class clazz = Student.class;
Constructor[] constructors = clazz.getDeclaredConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
System.out.println(constructor.getName());//构造方法的名字
System.out.println(constructor.getModifiers());//修饰符
}
}
public com.situ.day15.Student()
com.situ.day15.Student
1
private com.situ.day15.Student(java.lang.String)
com.situ.day15.Student
2
@Test
public void testConstructor2() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class clazz = Student.class;
//private Student(Integer id, String name, Integer age, String gender)
Constructor constructor = clazz.getDeclaredConstructor(Integer.class, String.class, Integer.class, String.class);
//在反射面前,私有的都可以访问
constructor.setAccessible(true);
//Student student = new Student(1, "zhansgan", 23, "男");
Student student = (Student) constructor.newInstance(1, "zhangsan", 23, "男");
System.out.println(student);
}
Student{id=1, name='zhangsan', age=23, gender='男'}
public void testMethod() throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
//Student student = new Student();
//student.setName("lisi");
Class clazz = Class.forName("com.situ.day15.Student");
//得到无参构造方法
Constructor constructor = clazz.getConstructor();
//调用newInstance实例化对象
Student student = (Student) constructor.newInstance();
//student.setName("lisi");
Method method = clazz.getMethod("setName", String.class);
method.invoke(student, "lisi");
System.out.println(student);
}