文章目录
反射
反射就是程序运行时获取任意类的内部信息
反射(Reflection) 是 Java 语言的一种动态特性,它允许程序在运行时获取任意类的内部信息(如构造方法、成员变量、方法、注解等),并且可以动态地创建对象、调用方法、访问字段,甚至修改私有成员。
在没有反射的情况下,我们写代码时对类的引用是静态的、确定的。比如 new UserService(),编译器必须知道 UserService 这个类存在。但有些场景下,我们无法在编译时确定要使用的类,例如:
-
框架开发:Spring 框架需要读取你写的类(如 @Controller、@Service),动态创建对象并管理它们。框架在编写时并不知道你的业务类名,只能在运行时通过反射去加载、实例化。
-
配置文件驱动:将类名写在配置文件中,程序运行时读取配置,然后使用反射加载该类。
-
动态代理:JDK 动态代理在运行时生成代理类,需要反射调用目标方法。
-
开发工具:IDE 的代码提示、调试器查看变量值等,底层都用到了反射。
反射API
Java 的反射 API 提供了一系列的类和接口来操作 Class 对象。主要的类包括:
- java.lang.Class:表示类的对象。提供了方法来获取类的字段、方法、构造函数等。
- java.lang.reflect.Field:表示类的字段(属性)。提供了访问和修改字段的能力。
- java.lang.reflect.Method:表示类的方法。提供了调用方法的能力。
- java.lang.reflect.Constructor:表示类的构造函数。提供了创建对象的能力。
Class类
Java文件编译成.class文件后,JVM会读取.class文件,并将其转化为java.lang.Class类的实例。这样,每个运行中的Java类都对应一个Class对象。通过反射机制可以访问并修改这个类的属性和方法,让类在运行时具有动态特性。
Class类中的相关方法如下,
- 获得类相关的方法
| 方法 | 用途 |
|---|---|
| getClassLoader() | 获得类的加载器 |
| getDeclaredClasses() | 返回一个数组,数组中包含该类中所有类和接口类的对象(包括私有的) |
| forName(String className) | 根据类名返回类的对象 |
| newInstance() | 创建类的实例 |
| getName() | 获得类的完整路径名字 |
- 获得类中属性相关的方法
| 方法 | 用途 |
|---|---|
| getField(String name) | 获得某个公有的属性对象 |
| getFields() | 获得所有公有的属性对象 |
| getDeclaredField(String name) | 获得某个属性对象 |
| getDeclaredFields() | 获得所有属性对象 |
- 获得类中属性相关的方法
| 方法 | 用途 |
|---|---|
| getAnnotation(Class<A> annotationClass) | 返回该类中与参数类型匹配的公有注解对象 |
| getAnnotations() | 返回该类所有的公有注解对象 |
| getDeclaredAnnotation(Class<A>annotationClass) | 返回该类中与参数类型匹配的所有注解对象 |
| getDeclaredAnnotations() | 返回该类所有的注解对象 |
- 获得类中构造器相关的方法
| 方法 | 用途 |
|---|---|
| getConstructor(Clas...<?>parameterTypes) | 获得该类中与参数类型匹配的公有构造方法 |
| getConstructors() | 获得该类的所有公有构造方法 |
| getDeclaredConstructor(Class...<?>parameterTypes) | 获得该类中与参数类型匹配的构造方法 |
| getDeclaredConstructors() | 获得该类所有构造方法 |
- 获得类中方法相关的方法
| 方法 | 用途 |
|---|---|
| getMethod(String name, Class...<?>parameterTypes) | 获得该类某个公有的方法 |
| getMethods() | 获得该类所有公有的方法 |
| getDeclaredMethod(String name,Class...<?> parameterTypes) | 获得该类某个方法 |
| getDeclaredMethods() | 获得该类所有方法 |
获取一个类的 Class 对象
Java 反射的入口是 java.lang.Class 类。获取一个类的 Class 对象有以下三种方式
- 通过对象.getClass()
- 通过类名.class
- 通过 Class.forName("全限定类名")
java
class Student {
//私有属性name
private String name = "bit";
//公有属性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 +
'}';
}
}
public class TestDemo {
public static void main(String[] args) {
/*
1.通过getClass获取Class对象
*/
Student s1 = new Student();
Class c1 = s1.getClass();
/*
2.直接通过 类名.class 的⽅式得到,该⽅法最为安全可靠,程序性能更⾼
这说明任何⼀个类都有⼀个隐含的静态成员变量 class
*/
Class c2 = Student.class;
/*
3、通过 Class 对象的 forName() 静态⽅法来获取,⽤的最多,
但可能抛出 ClassNotFoundException 异常
*/
Class c3 = null;
try {
//注意这⾥是类的全路径,如果有包需要加包的路径
c3 = Class.forName("Student");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//⼀个类在 JVM 中只会有⼀个 Class 实例,即我们对上⾯获取的
//c1,c2,c3进⾏ equals ⽐较,发现都是true
System.out.println(c1.equals(c2));
System.out.println(c1.equals(c3));
System.out.println(c2.equals(c3));
}
}
反射的使用
创建对象调用的是Student(),输出默认属性Student{name='bit',age=18}
java
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectClassDemo {
// 创建对象
public static void reflectNewInstance() {
try {
Class<?> classStudent = Class.forName("Student");
Object objectStudent = classStudent.newInstance();
Student student = (Student) objectStudent;
System.out.println("获得学⽣对象:"+student);
} catch (Exception ex) {
ex.printStackTrace();
}
}
// 反射私有的构造⽅法 屏蔽内容为获得公有的构造⽅法
public static void reflectPrivateConstructor() {
try {
Class<?> classStudent = Class.forName("Student");
//注意传⼊对应的参数
Constructor<?> declaredConstructorStudent =
classStudent.getDeclaredConstructor(String.class,int.class);
//Constructor<?> declaredConstructorStudent = classStudent.getConstructor();
//设置为true后可修改访问权限
declaredConstructorStudent.setAccessible(true);
Object objectStudent = declaredConstructorStudent.newInstance("⾼博",15);
//Object objectStudent = declaredConstructorStudent.newInstance();
Student student = (Student) objectStudent;
System.out.println("获得私有构造哈数且修改姓名和年龄:"+student);
} catch (Exception ex) {
ex.printStackTrace();
}
}
// 反射私有属性
public static void reflectPrivateField() {
try {
Class<?> classStudent = Class.forName("Student");
Field field = classStudent.getDeclaredField("name");
field.setAccessible(true);
//可以修改该属性的值
Object objectStudent = classStudent.newInstance();
Student student = (Student) objectStudent;
field.set(student,"⼩明");
String name = (String) field.get(student);
System.out.println("反射私有属性修改了name:"+ name);
} catch (Exception ex) {
ex.printStackTrace();
}
}
// 反射私有⽅法
public static void reflectPrivateMethod() {
try {
Class<?> classStudent = Class.forName("Student");
Method methodStudent =
classStudent.getDeclaredMethod("function",String.class);
System.out.println("私有⽅法的⽅法名为:"+methodStudent.getName());
//私有的⼀般都要加
methodStudent.setAccessible(true);
Object objectStudent = classStudent.newInstance();
Student student = (Student) objectStudent;
methodStudent.invoke(student,"我是给私有的function函数传的参数");
} catch (Exception ex) {
ex.printStackTrace();
}
}
public static void main(String[] args) {
//reflectNewInstance();
//reflectPrivateConstructor();
//reflectPrivateField();
reflectPrivateMethod();
}
}
反射的优缺点
优点:
- 对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法。
- 增加程序的灵活性和扩展性,降低耦合性,提高自适应能力。
- 反射已经运用在了很多流行框架如:Struts、Hibernate、Spring 等等。
缺点:
- 使用反射会有效率问题,会导致程序效率降低;
- 反射技术绕过了源代码的技术,因而会带来维护问题,反射代码比相应的直接代码更复杂。
反射在实际框架中的应用
- JDBC 加载数据库驱动:反射 + 类加载器
通过 Class.forName 动态加载驱动类
java
Class.forName("com.mysql.cj.jdbc.Driver");
- Spring IoC 容器:反射 + 注解 + 动态代理
Spring 在启动时会扫描包路径下的类,读取 @Component、@Service 等注解,然后通过反射创建 Bean 实例,并通过反射注入依赖(@Autowired)
- 配置文件驱动的应用(如 Spring Boot 的自动配置)
读取 META-INF/spring.factories 中的类名列表,通过反射实例化并注册