一、什么是 Java 反射
Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。反射的本质是得到 Class 对象后,反向获取 Class 对象的各种信息。
Java 中的反射(Reflection)是指在运行时动态获取一个类的信息,包括类名称、属性、方法等。通过反射,程序可以在运行时动态地创建对象、调用方法、修改属性等,而不需要在编译时确定这些信息。要使用反射,您需要使用 Java 的内置反射包 java.lang.reflect。
反射技术主要包括以下几个方面:
- 获取 Class 对象:Class 对象是描述类信息的对象,可以通过类名、对象实例、Class.forName()等方式获取。
- 创建对象:反射技术可以在运行时动态地创建对象,通过 Class 对象的 newInstance()方法或 Constructor类的 newInstance()方法都可以实现对象的创建。
- 获取和设置属性值:使用反射技术可以获取和设置类的属性值,使用类的 getField()、getDeclaredField()等方法可以获取属性对象,然后通过该对象的 get()、set()方法可以获取和设置属性值。
- 调用方法:使用反射技术可以调用类的方法,使用类的 getMethod()、getDeclaredMethod()等方法可以获取方法对象,然后通过该对象的 invoke()方法可以调用方法。
- 获取类信息:使用反射技术可以获取类的信息,例如类名、父类、实现接口、方法、属性等。
- 运行时注解处理:可以在运行时使用反射技术处理注解,可以使用 Class 的 getAnnotations()、getDeclaredAnnotations()等方法获取注解信息,然后使用注解处理器对信息进行处理。
反射技术的优点是允许在运行时获取类的信息并对其进行操作,增强了程序的灵活性,但同时也会带来性能上的瓶颈和安全性问题。因此,在使用反射技术时需要慎重考虑其用途和安全性。
反射是框架设计的灵魂,框架通常是半成品软件,可以在框架的基础上进行软件开发,简化编码。反射机制将类的各个组成部分封装为其他对象,好处是可以在程序运行过程中,操作这些对象,从而解耦,提高程序的可扩展性。
反射是通过 Class 对象(字节码文件),来知道某个类的所有属性和方法。也就是说通过反射我们可以获取构造器,对象,属性,方法(原本不知道)。在 Java 框架中,很多类我们是看不见的,不能直接用类名去获取对象,只能通过反射去获取。
获取 Class 对象的三种方式:
- 通过该类的对象去获取到对应的 Class 对象(基本不用它):
- student---\u003eClass对象,通过 getClass()方法。
- 例如:Student student = new Student();Class\u003c?\u003e Class stuClass = student.getClass();,获取到了对应的 Class 对象。但这种方式和反射机制相悖,有类对象还去用反射获取 Class 类对象多此一举。
- 通过类名 .class静态属性获取(比较简单):
- 每个类创建后都会有一个默认的静态的 class属性,用于返回该类的 class 对象。
- 例如:Class stuClass2 = Student.class;,需要注意的是,这种方式虽然比较简单,但是需要导包,不然会编译错误。
- 通过 Class类中的静态方法 forName()方法获取(最为常见):
- Class.forName(\"fanshe.Student\");,注意参数一定为该类的全限定类名。
- 结论:同一个字节码文件(.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的 Class 对象都是同一个。
二、获取 Class 文件对象的方式
. 通过类实例对象的 getClass () 方法获取 Class。
当我们已经有了一个类的实例对象时,可以通过该对象的getClass()方法来获取对应的Class对象。例如,假设有一个Student类的对象student,可以使用Class<?> Class stuClass = student.getClass();来获取Student类的Class对象。但是这种方式和反射机制的初衷有些相悖,因为如果已经有了类对象还去用反射获取Class类对象会显得多此一举。
2. 通过类的 Class 属性获取 Class。
每个类在创建后都会有一个默认的静态的class属性,我们可以通过这个属性来返回该类的class对象。例如,对于Student类,可以使用Class stuClass2 = Student.class;来获取Student类的Class对象。需要注意的是,这种方式虽然比较简单,但是需要导包,不然会编译错误。
3. 通过 Class 类的静态方法 forName (String className) 获取 Class。
可以使用Class类的静态方法forName(String className)来获取Class对象。例如,Class.forName("fanshe.Student");,注意参数一定为该类的全限定类名。结论是同一个字节码文件(.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个。
从多个参考资料中也可以看到获取Class对象的方式有多种。比如在一些博客中提到了类似的获取方式:
- 通过调用运行时类的属性:.class,这与通过类的Class属性获取Class对象类似。
- 通过运行时类的对象,调用getClass()方法,和第一种通过类实例对象获取Class的方式一致。
- 调用Class的静态方法forName(String classPath),是最为常见的获取方式,与我们提到的第三种方式相同。
- 使用类加载器获取Class对象,作为一种了解的方式,也在一些场景中有其特定的用途。
综上所述,我们有多种方式可以获取Class文件对象,不同的方式适用于不同的场景,可以根据实际需求进行选择。
三、Java 反射的作用
- 在运行时判断任意一个对象所属的类。反射提供了 getClass()方法,使得我们可以获取对象的运行时类信息。例如,Object obj = "Hello, Reflection!"; Class<?> objClass = obj.getClass();通过这个方法,我们可以判断一个对象属于哪个类。
- 在运行时构造任意一个类的对象。通过反射,我们可以使用 Class 对象的 newInstance()方法或 getDeclaredConstructor().newInstance()来动态地创建类的实例。比如,try{Class<?> stringClass = String.class;Object strInstance = stringClass.getDeclaredConstructor().newInstance();}注意如果类没有无参构造器,这种方式可能会抛出异常。
- 在运行时判断任意一个类所具有的成员变量和方法。通过反射,我们可以获取类的所有成员变量和方法,并可以检查它们的修饰符、参数等信息。例如,Class<?> stringClass = String.class;
- 获取所有公共字段:Field[] fields = stringClass.getFields();for(Field field : fields){System.out.println("公共字段: " + field.getName());}
- 获取所有方法(包括公共、保护、默认和私有):Method[] methods = stringClass.getDeclaredMethods();for(Method method : methods){System.out.println("方法: " + method.getName());}
- 在运行时调用任意一个对象的方法。通过反射,我们可以调用对象的任何可见或私有方法。例如,try{String str = "Hello";Class<?> stringClass = str.getClass();Method lengthMethod = stringClass.getMethod("length");int length = (int) lengthMethod.invoke(str);System.out.println("字符串长度: " + length);}通过反射可以调用对象的方法,即使这些方法在编写原始代码时并未预先定义,这使得开发者可以编写更加灵活和可扩展的代码。
四、Java 反射的优缺点
1. 优点
- 增加程序的灵活性:反射允许程序在运行时动态地访问和操作类的属性、方法和构造函数,这意味着开发者可以根据需要,在程序执行过程中改变程序的行为,而无需在编译时硬编码这些行为。
- 提高代码复用率:比如动态代理就是用到了反射机制,开发者可以编写一些通用的函数或方法,这些函数或方法能够动态地处理不同类型的对象或方法,有助于提高代码的复用率,减少重复代码的编写。
- 可以在运行时轻松获取任意一个类的方法、属性,并且还能通过反射进行动态调用:反射提供了getClass()方法,使得我们可以获取对象的运行时类信息,从而在运行时构造任意一个类的对象、判断任意一个对象所属的类、判断任意一个类所具有的成员变量和方法以及调用任意一个对象的方法。
2. 缺点
- 性能开销较大:反射操作通常比直接代码调用更慢,因为涉及到额外的检查和解析步骤。反射包括了一些动态类型,所以 JVM 无法对这些代码进行优化。例如,Class clazz = Class.forName("com.example.MyClass");Object instance = clazz.newInstance();Method method = clazz.getMethod("myMethod");method.invoke(instance);这样的反射调用比直接调用类的方法性能要差,在性能敏感的应用程序中频繁调用的代码段中应避免使用反射。
- 可能导致类型安全问题:使用反射可以绕过编译时类型检查,这可能导致类型安全问题。如果不小心操作对象的类型,可能会引发ClassCastException等异常。例如,Class clazz = Class.forName("com.example.MyClass");Object instance = clazz.newInstance();Method method = clazz.getMethod("myMethod");// 假设 myMethod 返回一个 String,但这是在运行时才知道的String result = (String) method.invoke(instance); // 可能引发 ClassCastException。
- 可维护性问题:由于反射允许操作私有方法和字段,这可能导致代码不可读和难以维护。此外,类结构的更改可能导致反射代码无法正常工作。
- 安全性问题:反射可以突破 Java 的访问控制机制,例如访问私有方法和属性。但是这种行为可能存在安全隐患,Java 提供了一些安全性检查机制,例如,如果调用私有方法或属性时没有通过setAccessible(true)方法设置为可访问,就会抛出IllegalAccessException异常。反射代码打破了抽象,可能会随着平台的升级而改变行为,破坏了代码本身的抽象性,引起一些安全问题。
五、Java 反射的使用场景
- 在运行时判断任意一个对象所属的类。
通过反射提供的getClass()方法,我们可以在运行时轻松确定一个对象所属的类。例如:Object obj = new String("Hello"); Class<?> objClass = obj.getClass();,这样就可以获取到对象obj所属的类信息。
- 在运行时构造任意一个类的对象。
Java 反射机制提供了多种方式在运行时构造类的对象。比如使用Class.forName().newInstance()方法,这种方式利用默认的无参数构造函数来创建对象,但如果类没有默认的无参数构造函数,就会抛出异常。另一种方式是使用Constructor.newInstance()方法,这个方法更加灵活,可以创建有参数构造函数的对象,也可以创建私有构造函数的对象,不过代码更复杂,执行效率也比直接使用newInstance方法低。例如:
Class<?> stringClass; try { stringClass = Class.forName("java.lang.String"); Object strInstance = stringClass.getDeclaredConstructor().newInstance(); } catch (Exception e) { e.printStackTrace(); } |
3. 在运行时判断任意一个类所具有的方法和属性。
通过反射,我们可以在运行时获取一个类的所有方法和属性信息。比如使用getFields()方法可以获取所有公共字段,使用getDeclaredMethods()方法可以获取所有方法(包括公共、保护、默认和私有)。例如:
Class<?> stringClass = String.class; Field[] fields = stringClass.getFields(); for(Field field : fields){ System.out.println("公共字段: " + field.getName()); } Method[] methods = stringClass.getDeclaredMethods(); for(Method method : methods){ System.out.println("方法: " + method.getName()); } |
4. 在运行时调用任意一个对象的方法。
利用反射,我们可以在运行时调用对象的任何可见或私有方法。例如:
try{ String str = "Hello"; Class<?> stringClass = str.getClass(); Method lengthMethod = stringClass.getMethod("length"); int length = (int) lengthMethod.invoke(str); System.out.println("字符串长度: " + length); } catch (Exception e) { e.printStackTrace(); } |
5. 生成动态代理。
Java 反射机制在生成动态代理方面起着重要作用。通过反射,可以在运行时创建代理对象,对目标对象的方法调用进行拦截和处理,实现诸如日志记录、性能监控等功能。
6.JDBC 的数据库连接。
在 JDBC 连接数据库中,加载驱动就是通过反射技术实现的。即引入相关 Jar 包后,使用Class.forName()方法加载数据库的驱动程序。
7.xml 或 properties 等配置文件加载。
在一些应用中,可以利用反射机制根据配置文件中的类名等信息动态地加载和实例化相应的类,实现灵活的配置管理。比如在 Spring 框架中,通过 XML 配置模式装载 Bean,就是反射的一个典型应用。将程序内 XML 配置文件加载入内存中,解析 xml 里面的内容,得到相关字节码信息,然后使用反射机制,得到 Class 实例,动态配置实例的属性,这样可以避免每次都去new实例,并且可以通过修改配置文件实现灵活配置。