一.反射的概念及定义
- 在日常的第三方应用开发过程中,经常会遇到某个类的某个成员变量、方法或是属性是私有的或是只对系统 应用开放,这时候就可以利用Java的反射机制通过反射来获取所需的私有成员或是方法 。
- 反射最重要的用途就是开发各种通用框架,比如在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的反射机制应用到这个实例,就可以去获得甚至去添加改变这个类的属性和动作,使得这个类成为一个动态的类 .
二.反射相关的示例
在Java中,反射通过java.lang.reflect
包中的类和接口来实现。
反射的基本信息:
-
Class
类:Class
是反射的核心类之一,它表示一个类或接口的运行时对象。通过Class
类可以获取和操作类的信息,如类的名称、父类、接口、构造函数、字段和方法等。 -
Constructor
类:Constructor
类表示类的构造函数。通过Constructor
类可以创建类的实例,调用构造函数并实例化对象。 -
Field
类:Field
类表示类的字段(成员变量)。通过Field
类可以获取和修改字段的值,以及访问字段的属性信息。 -
Method
类:Method
类表示类的方法。通过Method
类可以调用类的方法,传递参数并获取返回值。 -
获取
Class
对象:可以使用多种方式获取Class
对象,如使用类名调用Class.forName()
方法、通过类的实例调用getClass()
方法、或者直接通过类字面常量使用SomeClass.class
。 -
实例化对象:通过
Class
对象和Constructor
类可以实例化类的对象。可以使用newInstance()
方法创建无参构造函数的实例,或者使用Constructor
类的newInstance()
方法传递参数创建有参构造函数的实例。 -
调用方法:通过
Class
对象和Method
类可以调用类的方法。可以使用invoke()
方法传递对象和参数来调用方法,并获取返回值。 -
访问字段:通过
Class
对象和Field
类可以访问类的字段。可以使用get()
和set()
方法获取和修改字段的值,以及使用getField()
和getDeclaredField()
方法获取字段对象。
2.1获取Class对象的三种方式
Class
对象,因为
Class
对象提供了许多有用的方法和操作,用于在运行时检查和操作类的结构、属性和方法。
Class
对象,这个对象相当于是类的身份证。通过这个身份证,我们可以了解这个类的各种信息。
通过 Class
对象,我们可以做以下事情:
-
创建对象实例:通过
Class
对象的newInstance()
方法可以动态地创建类的对象实例。 -
获取类的信息:通过
Class
对象可以获取类的名称、修饰符、包名、父类、接口、字段和方法等信息。 -
获取和设置字段值:通过
Class
对象和字段名称,可以获取和设置类的字段的值。 -
调用方法:通过
Class
对象和方法名称,可以调用类的方法。 -
动态加载类:通过
Class.forName()
方法可以在运行时动态加载类。 -
进行注解处理:通过
Class
对象可以获取类上的注解信息,并进行相应的处理。
Class
对象,我们可以在运行时对类进行检查和操作,而无需提前知道类的具体信息。这使得代码更加灵活,可以在运行时根据需要动态地处理和操作类。
package demo1;
/**
* @Author 12629
* @Description:
*/
class Student {
//私有属性name
private String name = "Classmates";
//公有属性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 +
'}';
}
}
第一种,使用 Class.forName("类的全路径名"); 静态方法。(前提:已明确类的全路径名。)
public class Test {
public static void main(String[] args) {
//获取Class对象有三种方式之一
//第一种
Class<Student> c1 = null;
try {
c1 = Class.forName("demo1.Student");
}catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
这段代码的作用是将字符串 "demo1.Student"
作为参数传递给 Class.forName()
方法,并将返回的 Class
对象赋值给变量 c1
。
为什么有调异常呢?
这是因为 Class.forName()
方法尝试根据提供的类名加载对应的类,但如果在类路径中找不到该类,就会抛出 ClassNotFoundException
异常。
注意:使用 Class.forName()
方法时,需要提供类的全限定名,即包括包名和类名。
第二种:使用 .class 方法。(仅适合在编译前就已经明确要操作的 Class)
public class Test {
public static void main(String[] args) {
//第二种
Class<Student> c2 = Student.class;
}
}
段代码的作用是通过类字面常量 Student.class
来获取 Student
类的 Class
对象,并将其赋值给变量 c2
。
使用类字面常量的方式非常简单,只需要在类名后面加上 .class
就可以直接访问该类的 Class
对象。
public class Test {
//第三种
Student student = new Student();
Class<? extends Student> c3 = student.getClass();
}
我们创建了一个 Student
类的对象 student
,然后通过 student.getClass()
方法获取该对象的运行时类的 Class
对象,并将其赋值给类型为 Class<? extends Student>
的变量 c3
。
使用对象的 getClass()
方法可以在运行时获取对象所属类的 Class
对象。这种方式适用于当我们有一个对象实例,想要获取其对应的类信息时。
注意:一个类在 JVM 中只会有一个 Class 实例
public class Test {
/*
Class对象 只有一个
*/
public static void main(String[] args) {
//获取Class对象有三种方式
//生成的对象只有一个
//第一种
Class<Student> c1 = null;
try {
c1 = Class.forName("demo1.Student");
}catch (ClassNotFoundException e) {
e.printStackTrace();
}
//第二种
Class<Student> c2 = Student.class;
//第三种
Student student = new Student();
Class<? extends Student> c3 = student.getClass();
System.out.println(c1 == c2);
System.out.println(c1 == c3);
}
}
运行例图如下:
2.2 Class对象的使用
上一步我们已经创建好 Class
对象了,可以使用它来进行各种操作。
2.常用获得类中属性相关的方法(以下方法返回值为Field相关):
3. 获得类中构造器相关的方法(以下方法返回值为Constructor相关)
4.
4.获得类中方法相关的方法(以下方法返回值为Method相关)
5.获得类中注解相关的方法 (了解即可)
代码案例如下:
package demo1;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflectClassDemo {
/*reflectNewInstance() 方法使用反射创建一个类的实例,然后输出该实例。
它通过 Class.forName() 方法获取类的 Class 对象,然后使用 newInstance() 方法创建实例。
*/
public static void reflectNewInstance() {
Class<?> classStudent = null;
try {
// 获取类的Class对象
classStudent = Class.forName("demo1.Student");
// 使用newInstance()方法创建类的实例
Student student = (Student) classStudent.newInstance();
System.out.println(student);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
/*
reflectPrivateConstructor() 方法演示了如何使用反射调用私有的构造方法。
它通过 Class.forName() 方法获取类的 Class 对象,然后使用 getDeclaredConstructor() 方法获取私有构造方法,并通过 setAccessible(true) 设置访问权限。
最后,使用 newInstance() 方法创建实例并输出。
*/
public static void reflectPrivateConstructor() {
Class<?> classStudent = null;
try {
classStudent = Class.forName("demo1.Student");
// 获取私有构造方法
Constructor<?> constructor = classStudent.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true); // 设置私有构造方法可访问
// 调用私有构造方法创建实例
Student student = (Student) constructor.newInstance("xiaoming", 15);
System.out.println(student);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
/*
reflectPrivateField() 方法展示了如何使用反射操作私有字段。
它通过 Class.forName() 方法获取类的 Class 对象,然后使用 getDeclaredField() 方法获取私有字段,并通过 setAccessible(true) 设置访问权限。
接下来,使用 set() 方法给字段设置新的值,并输出结果。
*/
public static void reflectPrivateField() {
Class<?> classStudent = null;
try {
classStudent = Class.forName("demo1.Student");
// 获取私有字段
Field field = classStudent.getDeclaredField("name");
field.setAccessible(true); // 设置私有字段可访问(这个特别要注意,不然会报异常)
// 创建类的实例
Student student = (Student) classStudent.newInstance();
// 设置私有字段的值
field.set(student, "caocao");
System.out.println(student);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
/*
reflectPrivateMethod() 方法演示了如何使用反射调用私有方法。
它通过 Class.forName() 方法获取类的 Class 对象,然后使用 getDeclaredMethod() 方法获取私有方法,并通过 setAccessible(true) 设置访问权限。
最后,使用 invoke() 方法调用方法并输出结果。
*/
public static void reflectPrivateMethod() {
Class<?> classStudent = null;
try {
classStudent = Class.forName("demo1.Student");
// 获取私有方法
Method method = classStudent.getDeclaredMethod("function", String.class);
method.setAccessible(true); // 设置私有方法可访问
// 创建类的实例
Student student = (Student) classStudent.newInstance();
// 调用私有方法
method.invoke(student, "我是一个反射的参数!");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} 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);
}
}
public static void main(String[] args) {
// 反射实例化类
reflectNewInstance();
// 反射调用私有构造方法
reflectPrivateConstructor();
// 反射操作私有字段
reflectPrivateField();
//反射调用私有方法
reflectPrivateMethod();
}
}
运行例图如下:
三.总结
反射的优缺点如下:
优点:
-
动态性和灵活性:反射允许在运行时动态地获取和操作类的信息,使程序能够根据需要适应不同的情况和需求。它提供了灵活的实例化、字段访问和方法调用,以及动态代理和处理注解等功能,增强了程序的灵活性和可扩展性。
-
泛型操作:反射使得可以在运行时获取泛型类型的信息,并进行相应的操作。这对于编写通用代码和框架非常有用,可以在不知道具体类型的情况下进行更多的操作和处理。
-
框架和库的开发:反射广泛应用于框架和库的开发中。通过反射,可以在运行时动态地加载和使用类,根据配置文件或用户输入进行相应的操作,使框架和库具有更强的扩展性和适应性。
缺点:
-
性能影响:反射的操作通常比直接调用方法或访问字段的性能要低。使用反射会引入额外的开销,包括方法调用和类型检查等。因此,频繁使用反射可能导致程序的性能下降。
-
安全性问题:反射可以绕过访问权限的限制,可以访问和修改私有成员,并执行敏感操作。这可能导致安全性问题,特别是在处理不受信任的代码或用户输入时需要格外小心。
-
编码复杂性和可读性降低:反射的使用可能会增加代码的复杂性和可读性降低。由于反射是在运行时动态进行的,因此一些问题只能在运行时才能被发现,而不是在编译时。这可能导致调试和维护过程中的困难。
-
局限性:反射有一些局限性,例如无法操作编译时不存在的类、字段或方法;无法操作原始类型的字段等。此外,由于反射是基于运行时信息的,因此在某些情况下可能无法获得期望的结果。
反射是Java语言中的一项强大特性,它允许程序在运行时动态地获取、操作和修改类、对象、字段和方法的信息。通过反射,我们可以实现灵活的类实例化、字段访问和方法调用,以及处理注解和实现动态代理等功能。然而,反射的使用应谨慎,需要平衡灵活性、性能和安全性,并注意其局限性和注意事项。总而言之,反射为Java开发者提供了强大的工具,使得程序可以在运行时动态地适应不同的需求和场景。