java反射面试

发布时间:2026/10/1 9:49:31
java反射面试
反射是 Java 核心特性之一它允许程序在运行时获取类的完整信息属性、方法、构造器、注解等并能动态操作类的成员即使是私有成员。简单来说反射打破了 “编译期确定” 的限制让程序具备了动态操作类的能力。一、反射的核心概念与核心类反射的所有操作都基于java.lang.reflect包和java.lang.Class类核心类如下核心类 / 接口作用Class代表类的 “类对象”是反射的入口封装了类的所有元信息Field代表类的成员变量属性可获取 / 设置属性值Method代表类的方法可调用方法包括私有方法Constructor代表类的构造器可创建类的实例替代new关键字Modifier工具类解析成员的修饰符如public、private、static等Parameter代表方法 / 构造器的参数信息Java 8Annotation代表注解可获取类 / 方法 / 属性上的注解信息二、反射的核心步骤1. 获取Class对象反射的入口Class对象是反射的基础获取方式有 3 种方式 1通过类名.class编译期确定最安全ClassString stringClass String.class; ClassInteger intClass int.class; // 基本类型也有Class对象方式 2通过对象.getClass()运行时获取适用于已有实例String str hello; Class? extends String clazz str.getClass();方式 3通过Class.forName(全类名)动态加载最灵活需处理异常// 全类名 包名 类名例如 java.lang.String Class? clazz Class.forName(java.lang.String);注意Class.forName()会触发类的静态代码块执行而前两种方式不会。2. 获取类的基础信息通过Class对象可获取类的元信息示例Class? stringClass String.class; // 1. 获取类名相关 String simpleName stringClass.getSimpleName(); // 简单类名String String fullName stringClass.getName(); // 全类名java.lang.String String canonicalName stringClass.getCanonicalName(); // 规范名java.lang.String // 2. 获取父类 Class? superClass stringClass.getSuperclass(); // java.lang.Object // 3. 获取实现的接口 Class?[] interfaces stringClass.getInterfaces(); // 如 Serializable、Comparable // 4. 获取修饰符返回int需通过Modifier解析 int modifiers stringClass.getModifiers(); boolean isPublic Modifier.isPublic(modifiers); // true boolean isFinal Modifier.isFinal(modifiers); // trueString是final类三、反射操作构造器Constructor1. 获取构造器方法作用getConstructor(Class?...)获取公共的指定参数类型的构造器getConstructors()获取所有公共构造器getDeclaredConstructor(...)获取任意访问权限的指定参数类型构造器包括私有getDeclaredConstructors()获取所有构造器包括私有2. 使用构造器创建实例public class User { private String name; private int age; // 私有构造器 private User(String name) { this.name name; } // 公共构造器 public User(String name, int age) { this.name name; this.age age; } } // 反射操作构造器示例 public class ReflectConstructorDemo { public static void main(String[] args) throws Exception { ClassUser userClass User.class; // 1. 获取公共构造器并创建实例 ConstructorUser publicConstructor userClass.getConstructor(String.class, int.class); User user1 publicConstructor.newInstance(张三, 20); // 2. 获取私有构造器并创建实例需设置可访问 ConstructorUser privateConstructor userClass.getDeclaredConstructor(String.class); privateConstructor.setAccessible(true); // 突破私有访问限制 User user2 privateConstructor.newInstance(李四); } }关键访问私有构造器时必须调用setAccessible(true)关闭访问检查否则抛出IllegalAccessException。四、反射操作成员变量Field1. 获取成员变量方法作用getField(String name)获取公共的指定名称的成员变量getFields()获取所有公共成员变量getDeclaredField(...)获取任意访问权限的指定名称成员变量包括私有getDeclaredFields()获取所有成员变量包括私有2. 获取 / 设置成员变量值public class ReflectFieldDemo { public static void main(String[] args) throws Exception { User user new User(张三, 20); ClassUser userClass User.class; // 1. 获取私有属性name并修改值 Field nameField userClass.getDeclaredField(name); nameField.setAccessible(true); // 关闭访问检查 String oldName (String) nameField.get(user); // 获取值张三 nameField.set(user, 王五); // 设置值王五 // 2. 获取私有属性age并修改值 Field ageField userClass.getDeclaredField(age); ageField.setAccessible(true); ageField.setInt(user, 25); // 针对基本类型的便捷方法 System.out.println(user); // 输出User{name王五, age25} } }注意获取基本类型属性值可用getInt()、getBoolean()等便捷方法避免拆箱设置静态属性时set()方法的第一个参数传null因为静态属性属于类不属于实例。五、反射操作方法Method【核心重点】1. 获取方法方法作用getMethod(String name, Class?...)获取公共的指定名称 参数类型的方法包括父类的公共方法getMethods()获取所有公共方法包括父类的公共方法getDeclaredMethod(...)获取任意访问权限的指定名称 参数类型的方法仅当前类包括私有getDeclaredMethods()获取所有方法仅当前类包括私有2. 调用方法核心invoke()Method类的核心方法是invoke(Object obj, Object... args)obj调用该方法的实例静态方法传nullargs方法的参数列表返回值方法的执行结果无返回值则返回null。示例调用普通方法、私有方法、静态方法public class User { private String name; // 普通方法 public String getName() { return name; } // 私有方法 private void setName(String name) { this.name name; } // 静态方法 public static void printInfo(String msg) { System.out.println(静态方法 msg); } } public class ReflectMethodDemo { public static void main(String[] args) throws Exception { User user new User(); ClassUser userClass User.class; // 1. 调用私有方法 setName Method setNameMethod userClass.getDeclaredMethod(setName, String.class); setNameMethod.setAccessible(true); // 关闭访问检查 setNameMethod.invoke(user, 张三); // 调用实例方法 // 2. 调用公共方法 getName Method getNameMethod userClass.getMethod(getName); String name (String) getNameMethod.invoke(user); // 执行方法获取返回值 System.out.println(name name); // 输出张三 // 3. 调用静态方法 printInfo Method printInfoMethod userClass.getMethod(printInfo, String.class); printInfoMethod.invoke(null, 反射调用静态方法); // 静态方法传null } }3. 获取方法的详细信息Method method userClass.getDeclaredMethod(setName, String.class); // 获取方法名 String methodName method.getName(); // setName // 获取返回值类型 Class? returnType method.getReturnType(); // void // 获取参数列表 Parameter[] parameters method.getParameters(); for (Parameter param : parameters) { System.out.println(参数名 param.getName() 类型 param.getType()); } // 获取修饰符 int modifiers method.getModifiers(); boolean isPrivate Modifier.isPrivate(modifiers); // true六、反射操作注解反射可获取类 / 方法 / 属性上的注解包括运行时注解核心方法// 自定义运行时注解 Retention(RetentionPolicy.RUNTIME) // 必须是RUNTIME才能被反射获取 Target({ElementType.TYPE, ElementType.METHOD}) public interface MyAnnotation { String value(); int num() default 10; } MyAnnotation(value 用户类, num 20) public class User { MyAnnotation(value 获取名称方法) public String getName() { return 张三; } } // 反射获取注解示例 public class ReflectAnnotationDemo { public static void main(String[] args) throws Exception { ClassUser userClass User.class; // 1. 获取类上的注解 MyAnnotation classAnnotation userClass.getAnnotation(MyAnnotation.class); System.out.println(classAnnotation.value()); // 用户类 System.out.println(classAnnotation.num()); // 20 // 2. 获取方法上的注解 Method getNameMethod userClass.getMethod(getName); MyAnnotation methodAnnotation getNameMethod.getAnnotation(MyAnnotation.class); System.out.println(methodAnnotation.value()); // 获取名称方法 } }七、反射的优缺点优点动态性运行时动态获取类信息、调用方法适用于框架开发如 Spring、MyBatis灵活性可访问私有成员突破封装限制通用性编写通用工具类如对象拷贝、JSON 解析。缺点性能损耗反射跳过编译期检查运行时解析类信息性能比直接调用低可通过setAccessible(true)减少部分损耗破坏封装可访问私有成员违背面向对象的封装原则可读性差反射代码比直接调用更复杂调试难度高安全性风险可能绕过权限检查带来安全隐患。八、反射的典型应用场景框架开发Spring 的 IOC 容器通过反射创建 Bean 实例MyBatis 通过反射映射数据库结果集动态代理JDK 动态代理基于反射实现InvocationHandler工具类如BeanUtils对象属性拷贝、JacksonJSON 序列化 / 反序列化注解处理JUnit、Spring 注解的解析依赖反射插件化开发动态加载外部类并调用方法。九、Java 反射高频面试题一、基础概念类1. 什么是 Java 反射反射的核心作用是什么标准答案Java 反射是指程序在运行时而非编译期获取类的完整元信息属性、方法、构造器、注解、修饰符等并能动态操作类的成员包括私有成员的机制。反射的核心作用是打破编译期的静态限制赋予程序动态性运行时获取类的任意信息即使编译期未知类名动态创建类的实例、调用方法、修改属性无需提前通过new或直接调用的方式实现解耦是框架开发的核心基础如 Spring IOC、MyBatis 映射。核心考点区分 “编译期” 和 “运行时”、反射的核心价值动态性。2. 反射的入口是什么如何获取 Class 对象有几种方式标准答案反射的唯一入口是java.lang.Class类的对象类对象它封装了类的所有元信息所有反射操作都基于Class对象展开。获取Class对象有 3 种核心方式区别如下获取方式示例适用场景关键特点类名.classClassString cls String.class;编译期已知类名最安全、高效不触发类加载静态代码块不执行对象.getClass ()String str abc; Class? cls str.getClass();已有类的实例运行时获取返回实例的实际类型多态场景下精准Class.forName (全类名)Class? cls Class.forName(java.lang.String);编译期未知类名动态加载会触发类的加载执行静态代码块需处理ClassNotFoundException补充面试加分基本类型如int也有Class对象int.class但无实例数组的Class对象String[].class且同一维度的数组共享同一个Class对象如new String[10]和new String[20]的getClass()结果相同。核心考点3 种方式的区别尤其是Class.forName触发静态代码块、Class类的核心地位。二、核心操作类3. 如何通过反射创建类的实例有几种方式标准答案通过反射创建实例有 2 种核心方式优先推荐第一种更灵活方式 1通过Constructor类支持所有构造器包括私有// 1. 获取Class对象 ClassUser userClass User.class; // 2. 获取指定构造器私有构造器用getDeclaredConstructor ConstructorUser constructor userClass.getDeclaredConstructor(String.class, int.class); // 3. 私有构造器需关闭访问检查 constructor.setAccessible(true); // 4. 创建实例 User user constructor.newInstance(张三, 20);方式 2通过Class.newInstance()JDK 9 已废弃仅支持无参公共构造器// 仅适用于类有无参public构造器 User user (User) userClass.newInstance();面试加分Constructor.newInstance()是Class.newInstance()的替代方案支持有参构造器且能抛出具体异常后者仅抛出InstantiationException/IllegalAccessException私有构造器创建实例的核心是setAccessible(true)作用是关闭 JVM 的访问权限检查。核心考点两种方式的对比、私有构造器的实例化、setAccessible的作用。4. 如何通过反射调用私有方法 / 修改私有属性为什么能做到标准答案1调用私有方法的步骤// 1. 获取Class对象 ClassUser userClass User.class; User user new User(); // 2. 获取私有方法getDeclaredMethod仅当前类包括私有 Method privateMethod userClass.getDeclaredMethod(privateMethod, String.class); // 3. 关闭访问检查核心 privateMethod.setAccessible(true); // 4. 调用方法静态方法第一个参数传null privateMethod.invoke(user, 测试参数);2修改私有属性的步骤Field privateField userClass.getDeclaredField(privateField); privateField.setAccessible(true); privateField.set(user, 新值); // 静态属性第一个参数传null3为什么能访问私有成员Java 的访问权限public/private/protected是编译期检查而非运行时强制限制。setAccessible(true)会关闭AccessibleObjectField/Method/Constructor 的父类的访问检查开关让 JVM 在运行时跳过权限验证从而允许操作私有成员。核心考点setAccessible的作用、访问权限的检查阶段编译期 vs 运行时。5. getMethod () 和 getDeclaredMethod () 的区别标准答案这两个方法是反射获取方法的核心核心区别有 2 点维度getMethod(String name, Class?... paramTypes)getDeclaredMethod(...)访问权限仅能获取公共public方法能获取任意访问权限的方法private/protected/default/public方法来源包含当前类 父类 / 接口继承的公共方法仅包含当前类声明的方法不包含父类 / 接口的方法补充面试加分同理getField()/getDeclaredField()、getConstructor()/getDeclaredConstructor()遵循完全相同的规则若想获取父类的私有方法需先通过cls.getSuperclass()获取父类的Class对象再调用父类的getDeclaredMethod()。核心考点权限范围、方法来源当前类 vs 父类。三、原理 应用类6. 反射的优缺点是什么实际应用场景有哪些标准答案1优点动态性运行时动态操作类适配编译期未知的场景如框架加载配置文件指定的类灵活性突破封装限制可操作私有成员满足特殊业务需求解耦实现通用化工具 / 框架如 Spring IOC 无需硬编码创建对象。2缺点性能损耗反射需运行时解析元信息、跳过访问检查性能比直接调用低 10~100 倍可通过setAccessible(true)减少部分损耗或缓存Method/Field对象优化破坏封装直接操作私有成员违背面向对象的封装原则增加代码维护成本可读性差反射代码冗长如获取方法、处理异常调试难度高安全风险可能绕过权限校验如访问私有属性带来安全漏洞。3核心应用场景框架开发Spring IOC反射创建 Bean、MyBatis反射映射结果集、JUnit反射执行测试方法动态代理JDK 动态代理基于反射的InvocationHandler实现通用工具类BeanUtils对象属性拷贝、Jackson/GsonJSON 序列化 / 反序列化注解解析自定义注解的生效依赖反射获取注解信息插件化 / 热部署动态加载外部 jar 包中的类并调用方法。核心考点优缺点的落地场景、性能优化手段、框架应用的理解。7. 反射为什么性能差如何优化反射的性能标准答案1性能差的核心原因运行时解析元信息反射需要在运行时解析Class、Method等元数据而非编译期直接确定调用路径访问检查开销每次调用私有成员时JVM 会做访问权限检查即使setAccessible(true)首次仍需检查自动装箱 / 拆箱Method.invoke()的参数和返回值都是Object类型基本类型会触发自动装箱 / 拆箱异常处理反射方法会抛出大量检查型异常如NoSuchMethodExceptionJVM 异常处理开销高。2性能优化手段面试重点缓存反射对象将频繁使用的Method/Field/Constructor对象缓存到Map或静态变量中避免重复获取如 Spring 缓存 Bean 的Method对象关闭访问检查调用setAccessible(true)跳过 JVM 的权限检查可提升 2~3 倍性能使用第三方库优化如CGLib基于字节码生成比反射快、ReflectASM通过生成字节码替代反射避免频繁调用减少反射方法的调用次数如批量操作属性时一次反射获取所有属性而非多次获取使用方法句柄MethodHandleJDK 7 引入的MethodHandle是反射的高性能替代方案直接操作字节码性能接近直接调用。核心考点性能差的底层原因、可落地的优化方案尤其是缓存。8. 反射和动态代理的关系JDK 动态代理为什么必须实现接口标准答案1反射与动态代理的关系动态代理的核心是在运行时生成代理类而 JDK 动态代理完全依赖反射实现代理类的方法调用会转发到InvocationHandler.invoke()方法invoke()方法内部通过反射的Method.invoke()调用目标对象的真实方法简单说反射是 JDK 动态代理的实现基础没有反射就无法动态调用目标方法。2JDK 动态代理必须实现接口的原因JDK 动态代理生成的代理类会继承Proxy类public class $Proxy0 extends Proxy implements 目标接口而 Java 是单继承机制一个类只能继承一个父类因此代理类无法再继承目标类只能通过实现接口的方式代理目标方法。补充面试加分CGLib 动态代理不依赖反射基于 ASM 字节码生成也无需实现接口通过继承目标类实现性能比 JDK 动态代理更高Spring AOP 中若目标类实现接口则用 JDK 动态代理否则用 CGLib。核心考点动态代理的实现原理、JDK 代理的限制单继承。四、进阶 综合类9. 反射是否会破坏 Java 的封装性如何看待这个问题标准答案反射会破坏封装性但需辩证看待1破坏封装的体现面向对象的封装原则是 “将私有成员隐藏仅通过公共方法暴露接口”而反射可直接修改私有属性、调用私有方法绕过了设计好的公共接口导致代码逻辑混乱私有成员被外部直接修改可能违反类的内部约束如属性校验维护成本高后续修改私有成员的名称 / 类型时反射代码可能报错且难以排查。2辩证看待框架层面反射破坏封装是 “必要之恶”—— 框架需要动态操作类的成员来实现通用化如 Spring 注入私有属性若仅通过公共方法框架的灵活性会大幅降低业务代码层面应尽量避免使用反射操作私有成员遵循封装原则仅在特殊场景如通用工具类下使用并做好注释和异常处理规范层面可通过SecurityManager限制反射的权限禁止恶意代码访问私有成员。核心考点对面向对象原则的理解、反射使用的场景边界。10. 反射在 Spring 中的应用有哪些标准答案Spring 框架的核心功能几乎都依赖反射实现核心应用场景IOC 容器创建 BeanSpring 解析 XML / 注解如Component获取类名通过Class.forName()加载类通过Constructor.newInstance()反射创建 Bean 实例支持无参 / 有参构造器包括私有构造器通过Field.set()反射注入依赖属性即使是私有属性。AOP 动态代理JDK 动态代理通过反射的Method.invoke()调用目标方法解析Before/After等注解时通过反射获取方法的注解信息。注解解析解析Autowired/Value/RequestMapping等注解时通过反射获取类 / 方法 / 属性上的注解元信息根据注解信息执行逻辑如Autowired自动注入依赖。Bean 生命周期管理通过反射调用PostConstruct/PreDestroy注解的方法通过反射调用 InitializingBean 的afterPropertiesSet()方法。