从初学者到专家:Java反射的完整指南

本是古典 何须时尚 2024-04-26 01:40 143阅读 0赞

" class="reference-link">aa7010a4e7834e068abba9383d5a5232.png

00d91eb1ee7844829bfa70bb9b614cc9.png

#

一.反射的概念及定义

Java 的反射( reflection )机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性,既然能拿到那么,我们就可以修改部分类型信息;这种动态获取信 息以及动态调用对象方法的功能称为java语言的反射(reflection)机制。

反射的用途

  1. 在日常的第三方应用开发过程中,经常会遇到某个类的某个成员变量、方法或是属性是私有的或是只对系统 应用开放,这时候就可以利用Java的反射机制通过反射来获取所需的私有成员或是方法 。
  2. 反射最重要的用途就是开发各种通用框架,比如在spring中,我们将所有的类Bean交给spring容器管理,无 论是XML配置Bean还是注解配置,当我们从容器中获取Bean来依赖注入时,容器会读取配置,而配置中给的就是类的信息,spring根据这些信息,需要创建那些Bean,spring就动态的创建这些类。

反射不同时期的类型

Java程序中许多对象在运行时会出现两种类型:运行时类型**(RTTI)**和编译时类型。

例如:Person p = new Student();

  • 编译时类型是在编译时期确定的,它是变量声明时所使用的类型。在上面的示例代码中,变量p的编译时类型是Person,因为它是通过Person p的声明来定义的。
  • 运行时类型是在程序运行时确定的,它是实际分配给对象的类型。在上面的示例代码中,使用new Student()创建了一个Student对象,并将其赋值给变量p,因此在运行时,p的类型是Student

通过反射,可以获取对象的运行时类型。例如,可以使用p.getClass()方法获取p对象的实际类型(运行时类型),并进行相应的操作。

除了获取对象的运行时类型,反射还可以获取类的详细信息,包括类的名称、父类、接口、构造函数、字段和方法等。通过获取类的信息,可以做一些动态的操作,如动态创建对象、调用方法、访问字段等。

注意:Java文件被编译后,生成了.class文件,JVM此时就要去解读.class文件 ,被编译后的Java文件.class也被JVM解析为一个对象,这个对象就是 java.lang.Class .这样当程序在运行时,每个java文件就最终变成了Class类对象的一个实例。我们通过Java的反射机制应用到这个实例,就可以去获得甚至去添加改变这个类的属性和动作,使得这个类成为一个动态的类 .

二.反射相关的示例

879fd252ed8f43699e6f9666085796df.png

在Java中,反射通过java.lang.reflect包中的类和接口来实现。

反射的基本信息:

  1. ClassClass是反射的核心类之一,它表示一个类或接口的运行时对象。通过Class类可以获取和操作类的信息,如类的名称、父类、接口、构造函数、字段和方法等。
  2. ConstructorConstructor类表示类的构造函数。通过Constructor类可以创建类的实例,调用构造函数并实例化对象。
  3. Field类:Field类表示类的字段(成员变量)。通过Field类可以获取和修改字段的值,以及访问字段的属性信息。
  4. Method类:Method类表示类的方法。通过Method类可以调用类的方法,传递参数并获取返回值。
  5. 获取Class对象:可以使用多种方式获取Class对象,如使用类名调用Class.forName()方法、通过类的实例调用getClass()方法、或者直接通过类字面常量使用SomeClass.class
  6. 实例化对象:通过Class对象和Constructor类可以实例化类的对象。可以使用newInstance()方法创建无参构造函数的实例,或者使用Constructor类的newInstance()方法传递参数创建有参构造函数的实例。
  7. 调用方法:通过Class对象和Method类可以调用类的方法。可以使用invoke()方法传递对象和参数来调用方法,并获取返回值。
  8. 访问字段:通过Class对象和Field类可以访问类的字段。可以使用get()set()方法获取和修改字段的值,以及使用getField()getDeclaredField()方法获取字段对象。

2.1获取Class对象的三种方式

在使用反射时,我们需要先获取要反射的类的 Class 对象,因为 Class 对象提供了许多有用的方法和操作,用于在运行时检查和操作类的结构、属性和方法。

你可以这样理解:当我们使用反射时,需要先创建类的 Class 对象,这个对象相当于是类的身份证。通过这个身份证,我们可以了解这个类的各种信息。

通过 Class 对象,我们可以做以下事情:

  • 创建对象实例:通过 Class 对象的 newInstance() 方法可以动态地创建类的对象实例。
  • 获取类的信息:通过 Class 对象可以获取类的名称、修饰符、包名、父类、接口、字段和方法等信息。
  • 获取和设置字段值:通过 Class 对象和字段名称,可以获取和设置类的字段的值。
  • 调用方法:通过 Class 对象和方法名称,可以调用类的方法。
  • 动态加载类:通过 Class.forName() 方法可以在运行时动态加载类。
  • 进行注解处理:通过 Class 对象可以获取类上的注解信息,并进行相应的处理。

通过先创建类的 Class 对象,我们可以在运行时对类进行检查和操作,而无需提前知道类的具体信息。这使得代码更加灵活,可以在运行时根据需要动态地处理和操作类。

举例子解释:

1.先创建出Student类:

  1. package demo1;
  2. /**
  3. * @Author 12629
  4. * @Description:
  5. */
  6. class Student {
  7. //私有属性name
  8. private String name = "Classmates";
  9. //公有属性age
  10. public int age = 18;
  11. //不带参数的构造方法
  12. public Student(){
  13. System.out.println("Student()");
  14. }
  15. private Student(String name,int age) {
  16. this.name = name;
  17. this.age = age;
  18. System.out.println("Student(String,name)");
  19. }
  20. private void eat(){
  21. System.out.println("i am eat");
  22. }
  23. public void sleep(){
  24. System.out.println("i am pig");
  25. }
  26. private void function(String str) {
  27. System.out.println(str);
  28. }
  29. @Override
  30. public String toString() {
  31. return "Student{" +
  32. "name='" + name + '\'' +
  33. ", age=" + age +
  34. '}';
  35. }
  36. }

第一种,使用 Class.forName(“类的全路径名”); 静态方法。(前提:已明确类的全路径名。)

  1. public class Test {
  2. public static void main(String[] args) {
  3. //获取Class对象有三种方式之一
  4. //第一种
  5. Class<Student> c1 = null;
  6. try {
  7. c1 = Class.forName("demo1.Student");
  8. }catch (ClassNotFoundException e) {
  9. e.printStackTrace();
  10. }
  11. }
  12. }

这段代码的作用是将字符串 "demo1.Student" 作为参数传递给 Class.forName() 方法,并将返回的 Class 对象赋值给变量 c1

为什么有调异常呢?

这是因为 Class.forName() 方法尝试根据提供的类名加载对应的类,但如果在类路径中找不到该类,就会抛出 ClassNotFoundException 异常。

注意:使用 Class.forName() 方法时,需要提供类的全限定名,即包括包名和类名。

521e65ef936540cc977d8a8045a70d87.png


第二种:使用 .class 方法。(仅适合在编译前就已经明确要操作的 Class)

  1. public class Test {
  2. public static void main(String[] args) {
  3. //第二种
  4. Class<Student> c2 = Student.class;
  5. }
  6. }

段代码的作用是通过类字面常量 Student.class 来获取 Student 类的 Class 对象,并将其赋值给变量 c2

使用类字面常量的方式非常简单,只需要在类名后面加上 .class 就可以直接访问该类的 Class 对象。


第三种 ,使用类对象的 getClass() 方法

  1. public class Test {
  2. //第三种
  3. Student student = new Student();
  4. Class<? extends Student> c3 = student.getClass();
  5. }

我们创建了一个 Student 类的对象 student,然后通过 student.getClass() 方法获取该对象的运行时类的 Class 对象,并将其赋值给类型为 Class<? extends Student> 的变量 c3

使用对象的 getClass() 方法可以在运行时获取对象所属类的 Class 对象。这种方式适用于当我们有一个对象实例,想要获取其对应的类信息时。


注意:一个类在 JVM 中只会有一个 Class 实例

  1. public class Test {
  2. /*
  3. Class对象 只有一个
  4. */
  5. public static void main(String[] args) {
  6. //获取Class对象有三种方式
  7. //生成的对象只有一个
  8. //第一种
  9. Class<Student> c1 = null;
  10. try {
  11. c1 = Class.forName("demo1.Student");
  12. }catch (ClassNotFoundException e) {
  13. e.printStackTrace();
  14. }
  15. //第二种
  16. Class<Student> c2 = Student.class;
  17. //第三种
  18. Student student = new Student();
  19. Class<? extends Student> c3 = student.getClass();
  20. System.out.println(c1 == c2);
  21. System.out.println(c1 == c3);
  22. }
  23. }

运行例图如下:

f74b77449c90464ea545c13ee02640ca.png


2.2 Class对象**的使用**

上一步我们已经创建好 Class 对象了,可以使用它来进行各种操作。

注意:所有和反射相关的包都在 import java.lang.reflect 包下面。

使用前我们先了解Class类中的相关的方法有哪些

1.常用获得类相关的方法:

605171326ad345cfae2defc8f1d7a10f.png

2.常用获得类中属性相关的方法(以下方法返回值为Field相关):

597e3e05010f4b2e96d8732a9d8c9bcf.png

3. 获得类中构造器相关的方法(以下方法返回值为Constructor相关)

4.2b80ca83917047089ab4f1cb5ed29c5e.png

4.获得类中方法相关的方法(以下方法返回值为Method相关)

6528388a462c46528054b3fc6cfde289.png

5.获得类中注解相关的方法 (了解即可)

d3c392f9929144f4920303a29a767edc.png

代码案例如下:

  1. package demo1;
  2. import java.lang.reflect.Constructor;
  3. import java.lang.reflect.Field;
  4. import java.lang.reflect.InvocationTargetException;
  5. import java.lang.reflect.Method;
  6. public class ReflectClassDemo {
  7. /*reflectNewInstance() 方法使用反射创建一个类的实例,然后输出该实例。
  8. 它通过 Class.forName() 方法获取类的 Class 对象,然后使用 newInstance() 方法创建实例。
  9. */
  10. public static void reflectNewInstance() {
  11. Class<?> classStudent = null;
  12. try {
  13. // 获取类的Class对象
  14. classStudent = Class.forName("demo1.Student");
  15. // 使用newInstance()方法创建类的实例
  16. Student student = (Student) classStudent.newInstance();
  17. System.out.println(student);
  18. } catch (ClassNotFoundException e) {
  19. e.printStackTrace();
  20. } catch (InstantiationException e) {
  21. throw new RuntimeException(e);
  22. } catch (IllegalAccessException e) {
  23. throw new RuntimeException(e);
  24. }
  25. }
  26. /*
  27. reflectPrivateConstructor() 方法演示了如何使用反射调用私有的构造方法。
  28. 它通过 Class.forName() 方法获取类的 Class 对象,然后使用 getDeclaredConstructor() 方法获取私有构造方法,并通过 setAccessible(true) 设置访问权限。
  29. 最后,使用 newInstance() 方法创建实例并输出。
  30. */
  31. public static void reflectPrivateConstructor() {
  32. Class<?> classStudent = null;
  33. try {
  34. classStudent = Class.forName("demo1.Student");
  35. // 获取私有构造方法
  36. Constructor<?> constructor = classStudent.getDeclaredConstructor(String.class, int.class);
  37. constructor.setAccessible(true); // 设置私有构造方法可访问
  38. // 调用私有构造方法创建实例
  39. Student student = (Student) constructor.newInstance("xiaoming", 15);
  40. System.out.println(student);
  41. } catch (ClassNotFoundException e) {
  42. e.printStackTrace();
  43. } catch (NoSuchMethodException e) {
  44. throw new RuntimeException(e);
  45. } catch (InvocationTargetException e) {
  46. throw new RuntimeException(e);
  47. } catch (InstantiationException e) {
  48. throw new RuntimeException(e);
  49. } catch (IllegalAccessException e) {
  50. throw new RuntimeException(e);
  51. }
  52. }
  53. /*
  54. reflectPrivateField() 方法展示了如何使用反射操作私有字段。
  55. 它通过 Class.forName() 方法获取类的 Class 对象,然后使用 getDeclaredField() 方法获取私有字段,并通过 setAccessible(true) 设置访问权限。
  56. 接下来,使用 set() 方法给字段设置新的值,并输出结果。
  57. */
  58. public static void reflectPrivateField() {
  59. Class<?> classStudent = null;
  60. try {
  61. classStudent = Class.forName("demo1.Student");
  62. // 获取私有字段
  63. Field field = classStudent.getDeclaredField("name");
  64. field.setAccessible(true); // 设置私有字段可访问(这个特别要注意,不然会报异常)
  65. // 创建类的实例
  66. Student student = (Student) classStudent.newInstance();
  67. // 设置私有字段的值
  68. field.set(student, "caocao");
  69. System.out.println(student);
  70. } catch (ClassNotFoundException e) {
  71. e.printStackTrace();
  72. } catch (NoSuchFieldException e) {
  73. throw new RuntimeException(e);
  74. } catch (InstantiationException e) {
  75. throw new RuntimeException(e);
  76. } catch (IllegalAccessException e) {
  77. throw new RuntimeException(e);
  78. }
  79. }
  80. /*
  81. reflectPrivateMethod() 方法演示了如何使用反射调用私有方法。
  82. 它通过 Class.forName() 方法获取类的 Class 对象,然后使用 getDeclaredMethod() 方法获取私有方法,并通过 setAccessible(true) 设置访问权限。
  83. 最后,使用 invoke() 方法调用方法并输出结果。
  84. */
  85. public static void reflectPrivateMethod() {
  86. Class<?> classStudent = null;
  87. try {
  88. classStudent = Class.forName("demo1.Student");
  89. // 获取私有方法
  90. Method method = classStudent.getDeclaredMethod("function", String.class);
  91. method.setAccessible(true); // 设置私有方法可访问
  92. // 创建类的实例
  93. Student student = (Student) classStudent.newInstance();
  94. // 调用私有方法
  95. method.invoke(student, "我是一个反射的参数!");
  96. } catch (ClassNotFoundException e) {
  97. e.printStackTrace();
  98. } catch (NoSuchMethodException e) {
  99. throw new RuntimeException(e);
  100. } catch (InstantiationException e) {
  101. throw new RuntimeException(e);
  102. } catch (IllegalAccessException e) {
  103. throw new RuntimeException(e);
  104. } catch (InvocationTargetException e) {
  105. throw new RuntimeException(e);
  106. }
  107. }
  108. public static void main(String[] args) {
  109. // 反射实例化类
  110. reflectNewInstance();
  111. // 反射调用私有构造方法
  112. reflectPrivateConstructor();
  113. // 反射操作私有字段
  114. reflectPrivateField();
  115. //反射调用私有方法
  116. reflectPrivateMethod();
  117. }
  118. }

运行例图如下:

2c3f55a56ca4493f9ee019eb73511105.png

三.总结

反射的优缺点如下:

优点:

  1. 动态性和灵活性:反射允许在运行时动态地获取和操作类的信息,使程序能够根据需要适应不同的情况和需求。它提供了灵活的实例化、字段访问和方法调用,以及动态代理和处理注解等功能,增强了程序的灵活性和可扩展性。
  2. 泛型操作:反射使得可以在运行时获取泛型类型的信息,并进行相应的操作。这对于编写通用代码和框架非常有用,可以在不知道具体类型的情况下进行更多的操作和处理。
  3. 框架和库的开发:反射广泛应用于框架和库的开发中。通过反射,可以在运行时动态地加载和使用类,根据配置文件或用户输入进行相应的操作,使框架和库具有更强的扩展性和适应性。

缺点:

  1. 性能影响:反射的操作通常比直接调用方法或访问字段的性能要低。使用反射会引入额外的开销,包括方法调用和类型检查等。因此,频繁使用反射可能导致程序的性能下降。
  2. 安全性问题:反射可以绕过访问权限的限制,可以访问和修改私有成员,并执行敏感操作。这可能导致安全性问题,特别是在处理不受信任的代码或用户输入时需要格外小心。
  3. 编码复杂性和可读性降低:反射的使用可能会增加代码的复杂性和可读性降低。由于反射是在运行时动态进行的,因此一些问题只能在运行时才能被发现,而不是在编译时。这可能导致调试和维护过程中的困难。
  4. 局限性:反射有一些局限性,例如无法操作编译时不存在的类、字段或方法;无法操作原始类型的字段等。此外,由于反射是基于运行时信息的,因此在某些情况下可能无法获得期望的结果。

反射是Java语言中的一项强大特性,它允许程序在运行时动态地获取、操作和修改类、对象、字段和方法的信息。通过反射,我们可以实现灵活的类实例化、字段访问和方法调用,以及处理注解和实现动态代理等功能。然而,反射的使用应谨慎,需要平衡灵活性、性能和安全性,并注意其局限性和注意事项。总而言之,反射为Java开发者提供了强大的工具,使得程序可以在运行时动态地适应不同的需求和场景。

d1bdffdf5ac840a79f83d3c0762ca713.png

发表评论

表情:
评论列表 (有 0 条评论,143人围观)

还没有评论,来说两句吧...

相关阅读