Java 反射 (Reflection) 学习笔记详细版一、反射的基本概念1.1 什么是反射反射Reflection是 Java 语言的一个特性它允许程序在运行时Runtime获取类的信息如类名、父类、接口、字段、方法、构造器等并可以动态地创建对象、调用方法、访问字段。简单来说反射就是**“在运行时动态地操作类”**。核心类java.lang.Class。1.2 为什么需要反射动态性在编译时不知道具体要使用哪个类只能在运行时根据配置或用户输入决定。框架基石Spring、Hibernate、MyBatis、JUnit 等主流框架的核心机制都依赖反射。Spring 的依赖注入DIAOP 动态代理序列化/反序列化通用工具开发开发通用的工具类如 JSON 解析器、对象拷贝工具。二、获取 Class 对象的三种方式在操作反射之前必须先获取目标类的Class对象。publicclassUser{privateStringname;publicUser(){}publicUser(Stringname){this.namename;}publicvoidsayHello(){System.out.println(Hello, name);}}2.1 方式一Class.forName(全类名)最常用特点需要处理ClassNotFoundException适合类名在配置文件或数据库中动态获取的场景。副作用会触发类的初始化执行静态代码块。try{Class?clazzClass.forName(com.example.User);System.out.println(clazz.getName());}catch(ClassNotFoundExceptione){e.printStackTrace();}2.2 方式二类名.class特点最安全、性能最好不需要异常处理。适用编译时已知类名的场景。Class?clazzUser.class;2.3 方式三对象.getClass()特点需要已有对象实例。适用在对象内部或已有对象实例时获取类型信息。UserusernewUser(张三);Class?clazzuser.getClass();注意对于同一个类无论通过哪种方式获取Class对象在 JVM 中都是唯一的。三、反射的核心操作3.1 创建对象实例方法 AnewInstance()(已废弃不推荐)Java 9 后废弃因为无法指定构造参数且只能调用无参构造。// 不推荐Useruser(User)clazz.newInstance();方法 BConstructor.newInstance()(推荐)可以指定构造参数可以调用任意构造器。try{// 1. 获取构造器对象 (指定参数类型)Constructor?constructorclazz.getConstructor(String.class);// 2. 创建实例Useruser(User)constructor.newInstance(李四);user.sayHello();}catch(Exceptione){e.printStackTrace();}3.2 操作字段 (Field)获取字段getField(String name): 获取public字段包括父类。getDeclaredField(String name): 获取任意访问权限的字段仅限当前类不包括父类。设置和获取值需要调用setAccessible(true)来暴力反射突破private限制。try{UserusernewUser();// 获取 private 字段FieldnameFieldclazz.getDeclaredField(name);// 暴力反射取消 Java 语言访问检查nameField.setAccessible(true);// 设置值nameField.set(user,王五);// 获取值Stringname(String)nameField.get(user);System.out.println(名字是name);}catch(Exceptione){e.printStackTrace();}3.3 操作方法 (Method)获取方法getMethod(String name, Class?... parameterTypes): 获取public方法包括父类。getDeclaredMethod(String name, Class?... parameterTypes): 获取任意访问权限的方法仅限当前类。调用方法同样需要setAccessible(true)来调用private方法。try{UserusernewUser(赵六);// 获取方法 (参数类型必须匹配)MethodsayHelloMethodclazz.getDeclaredMethod(sayHello);// 暴力反射sayHelloMethod.setAccessible(true);// 调用方法 (第一个参数是对象实例静态方法传 null)sayHelloMethod.invoke(user);// 带参数的方法MethodsetNameMethodclazz.getDeclaredMethod(setName,String.class);setNameMethod.setAccessible(true);setNameMethod.invoke(user,钱七);}catch(Exceptione){e.printStackTrace();}3.4 获取类信息Class?clazzUser.class;// 1. 类名System.out.println(类名: clazz.getName());// 全限定名System.out.println(简单名: clazz.getSimpleName());// User// 2. 父类System.out.println(父类: clazz.getSuperclass());// 3. 实现的接口Class?[]interfacesclazz.getInterfaces();for(Class?iface:interfaces){System.out.println(接口: iface.getName());}// 4. 所有构造器Constructor?[]constructorsclazz.getConstructors();// 仅 publicConstructor?[]declaredConstructorsclazz.getDeclaredConstructors();// 所有// 5. 所有方法Method[]methodsclazz.getMethods();// 所有 public 方法含继承Method[]declaredMethodsclazz.getDeclaredMethods();// 当前类所有方法// 6. 所有字段Field[]fieldsclazz.getFields();// 所有 public 字段Field[]declaredFieldsclazz.getDeclaredFields();// 当前类所有字段四、反射的实战应用4.1 模拟 Spring 的依赖注入 (DI)假设有一个配置类通过反射将属性注入到对象中。// 配置类classConfig{publicstaticvoidinject(Objecttarget)throwsException{Class?clazztarget.getClass();// 遍历所有字段for(Fieldfield:clazz.getDeclaredFields()){// 检查是否有 Autowired 注解 (假设自定义了该注解)if(field.isAnnotationPresent(Autowired.class)){field.setAccessible(true);// 根据字段类型创建实例 (简化版)Objectinstancefield.getType().getDeclaredConstructor().newInstance();field.set(target,instance);}}}}// 自定义注解interfaceAutowired{}// 测试类classService{AutowiredprivateDaodao;publicvoiddoWork(){System.out.println(Service 使用 Dao: dao);}}classDao{}// 运行publicclassTestDI{publicstaticvoidmain(String[]args)throwsException{ServiceservicenewService();Config.inject(service);// 自动注入 Daoservice.doWork();}}4.2 通用对象拷贝工具将源对象的属性值复制到目标对象要求属性名和类型一致。publicclassBeanUtils{publicstaticvoidcopyProperties(Objectsource,Objecttarget)throwsException{Class?srcClasssource.getClass();Class?tgtClasstarget.getClass();// 获取源对象的所有字段for(FieldsrcField:srcClass.getDeclaredFields()){srcField.setAccessible(true);ObjectvaluesrcField.get(source);// 尝试在目标对象中找到同名同类型的字段try{FieldtgtFieldtgtClass.getDeclaredField(srcField.getName());tgtField.setAccessible(true);// 类型检查简化版实际需处理类型转换if(tgtField.getType().isAssignableFrom(srcField.getType())){tgtField.set(target,value);}}catch(NoSuchFieldExceptione){// 目标类没有该字段跳过}}}}五、反射的优缺点5.1 优点灵活性极高可以在运行时动态加载类、创建对象、调用方法实现高度解耦。框架基础是 Java 生态中各种框架Spring, MyBatis, Hibernate的核心。通用性可以编写通用的工具类处理任意类型的对象。5.2 缺点性能开销反射操作涉及动态解析比直接调用慢JIT 优化后差距缩小但仍存在。频繁调用建议setAccessible(true)后缓存Method或Field对象。安全性问题可以绕过访问控制private破坏封装性。代码可读性差反射代码通常比较晦涩调试困难编译期无法检查类型错误。内部暴露可能访问到不应暴露的内部实现细节。六、反射与泛型 (Type Erasure)6.1 类型擦除Java 的泛型在编译后会被擦除运行时无法直接获取泛型的实际类型参数。例如ListString在运行时只是List。6.2 如何获取泛型类型需要通过反射获取父类或接口的ParameterizedType。classMyListextendsArrayListString{}publicclassGenericTest{publicstaticvoidmain(String[]args)throwsException{Class?clazzMyList.class;// 获取父类TypegenericSuperclassclazz.getGenericSuperclass();if(genericSuperclassinstanceofParameterizedType){ParameterizedTypepType(ParameterizedType)genericSuperclass;Type[]actualTypeArgumentspType.getActualTypeArguments();for(Typetype:actualTypeArguments){System.out.println(泛型参数类型type.getTypeName());// 输出java.lang.String}}}}七、反射的常见面试题反射的原理是什么JVM 在类加载阶段将类信息加载到方法区Class对象是这些信息的入口。反射通过Class对象访问方法区中的元数据。getMethods()和getDeclaredMethods()的区别getMethods(): 返回当前类及其父类的所有public方法。getDeclaredMethods(): 返回当前类声明的所有方法包括private,protected,default不包含父类。如何优化反射的性能缓存Class、Method、Field对象避免重复获取。调用setAccessible(true)关闭访问检查。避免在循环中频繁使用反射。反射能绕过final修饰符吗可以修改final字段的值通过Field.setAccessible(true)但无法修改final方法或final类本身无法继承或重写。为什么 Spring 需要反射实现 IOC控制反转动态创建 Bean 并注入依赖。实现 AOP面向切面编程动态代理。处理注解扫描和解析自定义注解。八、总结反射是 Java 动态性的核心是框架开发的基石。核心步骤获取 Class 对象 - 获取构造器/方法/字段 - 调用/访问。使用时需注意性能开销和安全性避免滥用。掌握反射原理对于深入理解 Spring、MyBatis 等框架至关重要。提示在实际开发中除非编写框架或通用工具否则应尽量避免直接使用反射优先使用接口和多态来实现解耦。