Java中反射机制的使用详解
作者:_雨_
反射的基本介绍
Java的发射机制(Reflection)是指在运行时动态地获取类的信息并操作对象的能力。
Java的发射机制允许程序在运行时检查和操作任意一个类、方法、属性等的信息,包括了类名、方法名、属性名、参数列表以及访问修饰符等。
通过Java的发射机制,可以实现一些高级的功能,比如动态生成代理对象、动态生成类、动态配置对象等。同时,Java的一些框架也广泛应用了发射机制,比如Spring框架中大量使用了反射机制来实现依赖注入和AOP等功能。
Java中一些常用的反射API包括Class、Method、Field、Constructor等。其中,Class类是反射机制的核心,通过它可以获取一个类的所有信息。Method和Field则分别表示类中的方法和属性,Constructor则表示类中的构造方法。
Java的发射机制给Java编程带来了更大的灵活性,但同时也需要注意到发射机制的运行效率相对较低,同时也可能破坏类的封装性,因此在使用时需要谨慎考虑。
在了解反射的基本介绍之后,我们在来看看反射的需求,为什么会需要反射
先看一段代码
代码演示:
package com.reflection.question; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Properties; /** * 反射问题的引入 */ @SuppressWarnings({"all"}) public class ReflectionQuestion { public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { //根据配置文件 re.properties 指定信息, 创建Cat对象并调用方法hi //传统的方式 new 对象 -》 调用方法 // Cat cat = new Cat(); // cat.hi(); ===> cat.cry() 修改源码. //我们尝试做一做 -> 明白反射 //1. 使用Properties 类, 可以读写配置文件 Properties的底层就是HashTable Properties properties = new Properties(); properties.load(new FileInputStream("src\\re.properties"));//加载配置文件的键值对到properties对象 String classfullpath = properties.get("classfullpath").toString();//"com.hspedu.Cat" 根据键获取值 String methodName = properties.get("method").toString();//"hi" System.out.println("classfullpath=" + classfullpath); System.out.println("method=" + methodName); //2. 创建对象 , 传统的方法,行不通 =》 反射机制 //new classfullpath(); //3. 使用反射机制解决 //(1) 加载类, 返回Class类型的对象cls Class cls = Class.forName(classfullpath); //(2) 通过 cls 得到你加载的类 com.hspedu.Cat 的对象实例 Object o = cls.newInstance(); System.out.println("o的运行类型=" + o.getClass()); //运行类型 //(3) 通过 cls 得到你加载的类 com.hspedu.Cat 的 methodName"hi" 的方法对象 // 即:在反射中,可以把方法视为对象(万物皆对象) Method method1 = cls.getMethod(methodName); //(4) 通过method1 调用方法: 即通过方法对象来实现调用方法 System.out.println("============================="); method1.invoke(o); //传统方法 对象.方法() , 反射机制 方法.invoke(对象) } }
反射的需求
Java反射机制的需求主要有以下几个方面:
- 动态加载类和调用类方法:通过反射机制,可以在运行时动态的加载一个类,并调用该类的方法。这种方式可以实现更加灵活的代码编写,避免了在编译期就要确定所有的类和方法的限制。
- 实现通用框架:通过反射机制,可以实现通用的框架,比如ORM(对象-关系映射)框架、依赖注入框架、AOP(面向切面编程)框架等。这些框架需要在运行时动态的获取类的信息,然后进行相应的操作。
- 修改类的属性和方法:通过反射机制,可以在运行时修改类的属性和方法,从而实现更加灵活的代码编写。
- 实现序列化和反序列化:Java中的序列化和反序列化需要用到反射机制,通过反射机制可以获取对象的属性和方法,然后将其序列化为字节流或者反序列化为对象。
- 动态代理:通过反射机制,可以实现动态代理,即在运行时生成一个代理对象,通过代理对象来调用目标对象的方法,从而实现AOP等功能。
那么我们使用反射机制可以完成
1.在运行时判断任意一个对象所属的类
2.在运行时构造任意一个类的对象
3.在运行时得到任意一个类所具有的成员变量和方法
4.在运行时调用任意一个对象的成员变量和方法
5.生成动态代理
反射相关的主要类
1.java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象
2.java.lang.reflect.Method: 代表类的方法, Method对象表示某个类的方法
3.java.lang.reflect.Field: 代表类的成员变量,Field对象表示某个类的成员变量
4.java.lang.reflect.Constructor: 代表类的构造方法,Constructor对象表示构造器
代码演示:
注意:反射和我们正常的使用方式有些不同,我们正常调用方法时对象名.方法名,但是在反射中是 方法名.对象名这里要注意一下
package com.reflection; import java.io.FileInputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Properties; @SuppressWarnings({"all"}) public class Reflection01 { public static void main(String[] args) throws Exception { //1. 使用Properties 类, 可以读写配置文件 Properties properties = new Properties(); properties.load(new FileInputStream("src\\re.properties")); String classfullpath = properties.get("classfullpath").toString();//"com.hspedu.Cat" String methodName = properties.get("method").toString();//"hi" //2. 使用反射机制解决 //(1) 加载类, 返回Class类型的对象cls Class cls = Class.forName(classfullpath); //(2) 通过 cls 得到你加载的类 com.hspedu.Cat 的对象实例 Object o = cls.newInstance(); System.out.println("o的运行类型=" + o.getClass()); //运行类型 //(3) 通过 cls 得到你加载的类 com.hspedu.Cat 的 methodName"hi" 的方法对象 // 即:在反射中,可以把方法视为对象(万物皆对象) Method method1 = cls.getMethod(methodName); //(4) 通过method1 调用方法: 即通过方法对象来实现调用方法 System.out.println("============================="); method1.invoke(o); //传统方法 对象.方法() , 反射机制 方法.invoke(对象) //java.lang.reflect.Field: 代表类的成员变量, Field对象表示某个类的成员变量 //得到name字段 //getField不能得到私有的属性 Field nameField = cls.getField("age"); // System.out.println(nameField.get(o)); // 传统写法 对象.成员变量 , 反射 : 成员变量对象.get(对象) //java.lang.reflect.Constructor: 代表类的构造方法, Constructor对象表示构造器 Constructor constructor = cls.getConstructor(); //()中可以指定构造器参数类型, 返回无参构造器 System.out.println(constructor);//Cat() Constructor constructor2 = cls.getConstructor(String.class); //这里传入的 String.class 就是String类的Class对象 System.out.println(constructor2);//Cat(String name) } }
反射优点和缺点
优点:
- 动态性:Java反射机制可以在运行时动态地获取类的信息和操作对象,使程序更加灵活和易于扩展。
- 通用性:可以通过反射机制操作任意类型的对象,无需知道对象的具体类型。
- 代码复用性:反射机制可以让代码更加通用和复用,提高开发效率。
- AOP支持:反射机制是实现AOP(面向切面编程)的重要手段之一。
缺点:
- 性能较低:Java反射机制需要在运行时动态地获取对象信息和方法,需要一定的时间开销,因此性能较低。
- 安全问题:反射机制可以操作任意类型的对象,容易造成安全问题,需要谨慎使用,并在必要时进行安全检查。
- 可读性下降:由于反射机制支持动态的获取和操作对象,因此代码的可读性会下降一些,需要谨慎使用。
- 不利于编译器优化:由于反射机制需要在运行时动态获取对象信息和方法,因此编译器很难对代码进行优化。
代码演示;
在下面的代码中 ,我们分别测试了,使用正常的方式去调用方法,和使用反射的方式去调用方法,并且在执行方法开始之前记录了开始时间,在方法执行结束之后,记录了结束时间,最后两个相减,就可以得到方法的执行时间,我们就可以看出,使用正常的方式去调用方法,和使用反射的方式去调用方法,的区别。
package com.reflection; import com.Cat; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * 测试反射调用a的性能,和优化方案 */ public class Reflection02 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { //Field //Method //Constructor m1(); m2(); m3(); } //传统方法来调用hi public static void m1() { Cat cat = new Cat(); long start = System.currentTimeMillis(); for (int i = 0; i < 90; i++) { cat.hi(); } long end = System.currentTimeMillis(); System.out.println("m1() 耗时=" + (end - start)); } //反射机制调用方法hi public static void m2() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { Class cls = Class.forName("com.Cat"); Object o = cls.newInstance(); Method hi = cls.getMethod("hi"); long start = System.currentTimeMillis(); for (int i = 0; i < 900000000; i++) { hi.invoke(o);//反射调用方法 } long end = System.currentTimeMillis(); System.out.println("m2() 耗时=" + (end - start)); } //反射调用优化 + 关闭访问检查 public static void m3() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { Class cls = Class.forName("com.Cat"); Object o = cls.newInstance(); Method hi = cls.getMethod("hi"); hi.setAccessible(true);//在反射调用方法时,取消访问检查 long start = System.currentTimeMillis(); for (int i = 0; i < 900000000; i++) { hi.invoke(o);//反射调用方法 } long end = System.currentTimeMillis(); System.out.println("m3() 耗时=" + (end - start)); } }
反射调用优化
Java中反射调用通常比直接调用方法缓慢,因为它需要进行额外的动态类型检查和查找。为了优化反射调用,可以采用以下几种方法:
缓存Method对象:通过缓存Method对象,避免动态查找,提高调用效率。
使用方法句柄(MethodHandle):方法句柄是一种更高效的调用机制,它可以绕过反射调用的开销。方法句柄比反射调用更快,特别是在热代码路径上。
使用invokedynamic指令:为了提高反射调用的性能,Java 7引入了invokedynamic指令。它允许动态绑定方法调用,避免了反射调用的开销。
使用字节码生成库:字节码生成库可以生成字节码,从而避免了反射调用的开销。比如,cglib是一个非常常用的字节码生成库。
到此这篇关于Java中反射机制的使用详解的文章就介绍到这了,更多相关Java反射机制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!