简介《Java反射面试题11题》面向Java求职者与初中级开发者聚焦运行时获取类信息、动态调用方法与创建对象等高频考点可用于面试前系统复习和日常查漏补缺。资源包仅含1个PDF文件约569KB内容集中、查阅方便。正文以11个问答梳理反射知识体系涵盖除new外的对象创建方式、反射与new的效率对比、JDBC及Hibernate、Spring等框架中的典型应用并介绍Class、Field、Constructor、Method等核心类获取Class对象的多种途径与newInstance()动态创建实例的步骤同时分析反射优缺点提及setAccessible(true)、缓存和ReflectASM等优化思路。已有230人学习适合希望理解反射原理、补齐面试表述并掌握基本API用法的读者参考。1. 到底什么是 Java 反射不用 new 也能把对象“叫”出来你可能在面试题里见过一个古怪问题除了 new还能怎么创建对象第一次听到 Java 反射很多人会把它当成黑客技巧。其实它更像酒店的“总控房卡”程序运行到一半拿到这张卡就能查到某个类有哪些房间、哪些服务、从哪个入口进去再按名字去调用。反射说的是程序在运行时对任意类都能知道它的属性和方法对任意对象都能调用它的方法给定类名就能获得类的信息并动态操作。它解决的是“写代码时还不知道具体类运行时再决定”的问题。2. 反射为什么存在从写死到运行时才决定的自由2.1 写死对象的世界为什么不够用普通代码创建对象很直接需要什么类就写new 类名()。编译器在编译阶段已经知道类型知道构造方法长什么样也知道这个对象能调用哪些方法。这样的写法并不丢人反而是大多数业务代码最该用的方式。问题出在另一类场景写代码的人并不知道明天会加载哪个类甚至不知道类名会不会从配置文件里传进来。打个比方普通new像你亲自去餐厅点菜菜单上的菜名你早就看过反射更像你只拿到一张写着菜名的纸条到了餐厅才让服务员按纸条去找厨房。纸条上的名字可能是运行时才传进来的程序不能提前把菜名写死在代码里。配置驱动的对象创建、框架扫描类、通用工具读取属性、数据库驱动加载都会遇到这种“名字先到、类后到”的情况。如果所有地方都写死扩展一个新实现就要改代码、重新编译。反射把“找类、看构造器、调方法”这些动作搬到运行时让程序先拿到一个字符串或Class对象再决定怎么操作。这就是它存在的根本理由不是为了炫技而是为了让程序在编译期少做死决定。2.2 反射把“决定权”推迟到运行时反射的核心动作可以拆成三步先拿到Class对象再通过它获取属性、方法、构造器信息最后调用或创建对象。Class对象像是类的运行时身份证程序拿着它去问这个类有哪些字段哪些方法构造方法需要几个参数问清楚之后才真正执行。这种“先问再做”的方式带来了灵活性。比如一个通用配置加载器配置文件里写的是类名程序启动时读到字符串用Class.forName找到类再调用构造器创建对象。代码里没有写死具体类换一个实现只需要改配置。再比如某些框架要统一处理一批类它不可能为每个类手写一段new只能靠反射在运行时读取信息再决定怎么实例化、怎么调用。反射还能让程序访问到一些普通调用碰不到的信息例如私有字段、私有方法、构造方法参数类型。注意这里的“能访问”不等于“应该访问”。反射给了程序一把万能钥匙但万能钥匙不是让你见门就开。它更适合框架、容器、通用工具这类“需要处理未知类”的位置而不是普通业务里为了少写一行代码就绕路。2.3 动态性的代价为什么 new 通常更快反射创建对象通常比new慢这一点在面试题里经常被问。原因不玄学new是编译器已经确定好的直接创建JVM 知道类型也知道该调用哪个构造器反射则要先找类资源使用类加载器完成加载再解析字节码、查找构造器、做访问检查最后才创建对象。步骤多了速度自然容易吃亏。对比项new 创建对象反射创建对象类型确定时间编译期写死运行时按类名或 Class 对象确定典型步骤直接分配对象并调用构造器找类、查构造器、访问检查、调用速度感受通常更快通常更慢缓存后可缓解适合场景普通业务对象框架、配置驱动、通用工具这个差距在偶尔创建一次对象时未必明显但在高频循环里会被放大。优化思路也不是没有多次创建同一个类的实例时可以把Class对象、构造器或方法对象缓存起来避免反复查找调用setAccessible(true)可以关闭安全检查提升反射速度但它同时绕过了封装限制用之前要想清楚后果还有一些工具会走字节码生成的方式加速本质上是把运行时的查找成本提前摊掉。注意反射的性能问题要看位置。启动阶段读配置、框架初始化时创建少量对象慢一点通常可以接受真正的高频热路径能写死就写死别为了“看起来灵活”让每次调用都绕一圈。3. 反射的四个核心角色Class、Field、Method、Constructor 到底各管什么3.1 Class反射的总入口Class是反射的核心类表示正在运行的 Java 应用程序中的类和接口。程序想获取某个类的信息第一步几乎都是先拿到它的Class对象。拿到之后才能继续问这个类有哪些属性、哪些方法、哪些构造方法。可以把Class理解成一张“类信息总台”。它不直接等于某个对象而是代表这个类本身的运行时描述。比如有一个DemoBean类DemoBean.class拿到的是这个类的描述new DemoBean().getClass()拿到的是这个对象所属类的描述Class.forName(com.example.reflect.DemoBean)则是按字符串去找这个类的描述。入口不同最终都指向同一张“总台”。Class对象能做的事情很多获取方法数组、字段数组、构造器数组按名称找单个方法或字段判断类型创建实例。面试题里常问“获取 Class 对象有几种方法”本质上是在问你从哪个入口拿到这张总台凭证。入口选得对不对会影响代码的时机和灵活性。3.2 Field、Method、Constructor属性、方法、构造器的“说明书”Class是总入口但具体到某个属性、某个方法、某个构造器反射又拆出了三个类。Field表示类的成员变量用来获取和设置属性值Method表示类的方法用来获取方法信息或者执行方法Constructor表示类的构造方法用来获取构造方法信息并创建对象。这三个类像是三份不同的说明书。Field说明书告诉你字段叫什么、类型是什么、能不能访问Method说明书告诉你方法名、参数类型、返回值Constructor说明书告诉你构造方法需要几个参数、参数分别是什么类型。没有它们Class只能告诉你“这个类有很多成员”但没法精确地操作某一个成员。public class DemoBean { private String name; private int age; public DemoBean() { } public DemoBean(String name, int age) { this.name name; this.age age; } public String sayHello(String who) { return hello who , I am name; } private void hiddenAction() { // 私有方法普通调用在类外访问不到 } }这段代码定义了一个用于演示的类字段name和age都是私有的构造方法有两个一个无参一个带两个参数方法sayHello接收一个字符串参数。反射要做的就是在类外面拿到这些信息用Field看name和age用Constructor看两个构造方法用Method看sayHello和hiddenAction。参数说明很简单String name表示姓名int age表示年龄sayHello的who表示向谁打招呼。代码后面的逻辑说明比代码本身更重要反射不是绕过类定义而是先读取类定义再按读取到的信息操作。3.3 为什么反射 API 要拆成四个类如果只用一个“万能类”包办所有事情反射 API 会变得很臃肿。拆成Class、Field、Method、Constructor是为了让职责清楚Class管整体Field管属性Method管方法Constructor管构造器。你拿到一个Field对象就知道它对应某个字段拿到一个Method对象就知道它对应某个方法。操作对象不同能调用的方法也不同。这种拆分还带来一个好处查找和操作可以分开。先用clazz.getDeclaredFields()找到一批字段再决定操作哪一个先用clazz.getDeclaredMethod(sayHello, String.class)找到方法再决定传什么参数调用。查找结果可以缓存操作动作可以复用。框架里常见的做法就是启动时扫描一批类把方法、字段、构造器信息存起来后面处理请求时直接使用缓存结果避免每次都重新查找。提示getDeclaredMethods()、getDeclaredFields()、getDeclaredConstructors()取到的是本类声明的成员包含私有成员不带Declared的获取方式通常只覆盖公开成员并且可能包含继承来的成员。面试里问“怎么获取所有方法”要先确认对方说的是“本类所有”还是“公开可访问”。4. 获取 Class 对象的几种方式与创建实例同一件事的不同入口4.1 四种拿 Class 对象的方式什么时候用哪种获取Class对象常见有四条路。第一条是Class.forName(类的全路径)用字符串加载类最常用适合类名来自配置或运行时的场景。第二条是类名.class编译期就能确定写法简洁适合代码里已经知道类的情况。第三条是对象名.getClass()前提是已经有一个对象适合从实例反查它的类信息。第四条是基本类型的包装类调用TYPE属性比如Integer.TYPE拿到的是基本类型的Class对象不是包装类本身。这四条路没有绝对优劣差别在“你手里先有什么”。手里只有字符串就用Class.forName手里已经写死了类名就用类名.class手里已经有对象就用getClass()要处理基本类型就用包装类的TYPE。面试题常把Class.forName说成最常用或性能较好的入口实际写代码时还要看场景。字符串加载的代价是字符串可能写错编译期发现不了类名.class的代价是类名必须提前知道。4.2 用 Class.newInstance() 与 Constructor.newInstance() 创建对象拿到Class对象后创建实例有两条常见路径。第一条是调用Class对象的newInstance()它要求目标类有一个默认的空构造器。第二条是先获取指定的Constructor对象再调用它的newInstance()这种方式可以选定构造方法也能传入参数。Class.newInstance()写法短但限制明显没有无参构造器就会失败异常处理也比较粗糙。Constructor.newInstance()更灵活能指定参数类型能选择带参构造器异常信息也更清楚。面试题里常把两种方式放在一起问真正写代码时更推荐第二种尤其是目标类没有无参构造器的时候。创建对象只是第一步创建之后还可以继续用Field设置属性、用Method调用方法。反射的完整使用步骤可以记成获取Class对象调用Class中的方法获取信息使用反射 API 操作这些信息。这三步里第一步是入口第二步是查找第三步才是执行。很多人一上来就写调用结果方法名或参数类型对不上直接翻车。4.3 一段可复现的反射操作示例下面这段代码把获取类、查看成员、创建对象、设置属性、调用方法串在一起。类名使用示例包名实际运行时换成你自己的类即可。import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; public class ReflectDemo { public static void main(String[] args) throws Exception { // 1. 按全路径获取 Class 对象 Class? clazz Class.forName(com.example.reflect.DemoBean); // 2. 获取本类声明的所有方法信息 Method[] methods clazz.getDeclaredMethods(); for (Method method : methods) { System.out.println(方法: method.toString()); } // 3. 获取本类声明的所有字段信息 Field[] fields clazz.getDeclaredFields(); for (Field field : fields) { System.out.println(字段: field.toString()); } // 4. 获取指定构造方法并创建对象 Constructor? constructor clazz.getDeclaredConstructor(String.class, int.class); Object bean constructor.newInstance(A同学, 20); // 5. 获取私有字段并设置值 Field nameField clazz.getDeclaredField(name); nameField.setAccessible(true); // 关闭访问检查才能操作私有字段 nameField.set(bean, B同学); // 6. 获取方法并调用 Method helloMethod clazz.getDeclaredMethod(sayHello, String.class); Object result helloMethod.invoke(bean, 反射); System.out.println(result); } }这段代码的关键参数需要说明Class.forName里传的是类的全路径包名和类名都不能少getDeclaredConstructor(String.class, int.class)里传的是构造方法参数类型顺序要和构造方法声明一致setAccessible(true)关闭访问检查让私有字段可以被读写但它不是“随便改私有”的许可证invoke(bean, 反射)的第一个参数是目标对象后面是方法实参。代码背后的逻辑是先找到类再找到构造器创建对象然后找到字段和方法最后执行。查找动作可以缓存尤其是构造器、方法、字段对象不要放在高频循环里反复获取。setAccessible(true)要谨慎使用关闭安全检查能提升一些速度也会让封装边界变薄。示例里改的是示例类真实项目里改私有字段之前先问一句这个字段为什么被设计成私有5. 常见误区与易混概念反射不等于动态代理也不等于随便改私有5.1 反射和动态代理都带“动态”但不是一回事易混点很多人看到“运行时动态”就把反射和动态代理当成同一个东西。为什么混因为动态代理在实现时确实会用到反射两者都发生在运行期名字里又都有“动态”的味道。区分方法看目标反射解决的是“我想知道这个类有什么、怎么调用它”动态代理解决的是“我想在调用某个方法前后插入一段逻辑并生成一个代理对象”。反射像拿着说明书拆解一台机器动态代理像在机器外面套一个中转站。中转站可以把请求转给原对象也可以在转之前记录日志、做权限判断。代理对象怎么调用到原方法底层会用到反射或类似机制但反射本身不负责生成代理。面试里如果问“反射和动态代理的区别”别只答“都用反射”要答出“反射是运行时获取和操作类信息动态代理是运行时生成代理对象并增强调用”。5.2 反射慢和反射不能用性能判断要看位置易混点一听说反射比new慢就得出“反射不能用在生产代码”的结论。为什么混因为性能对比表往往只比较单次创建没比较使用位置。启动时创建几个对象慢几毫秒用户根本感知不到高频请求里每次反射查找方法差距就会被放大。区分方法看三个问题调用频率高不高查找结果能不能缓存这个位置能不能在编译期写死普通业务对象的创建能new就new框架初始化、配置加载、通用工具反射的灵活性更值钱。真正要优化时先缓存Class、Constructor、Method再考虑关闭安全检查最后才考虑字节码生成之类的重手段。血泪经验是不要为了追求“零反射”把框架代码改得面目全非也不要为了“灵活”让热路径每次都用字符串找类。5.3 setAccessible(true) 和随便改私有能访问不等于该访问易混点setAccessible(true)一加私有字段和私有方法就能碰了于是有人把它当成万能后门。为什么混因为反射确实能突破普通访问限制代码跑起来也不报错容易让人产生“既然能改那就随便改”的错觉。区分方法看边界setAccessible(true)只是关闭访问检查它不改变字段的语义也不保证后续版本还安全。私有字段之所以私有往往是因为类内部有约束条件直接改可能绕过校验。框架用它做对象映射、依赖注入是因为框架承担了统一规则普通业务代码用它改另一个类的私有状态通常是在给未来埋坑。能访问是能力问题该不该访问是设计问题这两件事不能混。5.4 几种获取 Class 的方式入口不同时机不同易混点Class.forName(com.example.reflect.DemoBean)、DemoBean.class、new DemoBean().getClass()看起来都能拿到同一个Class对象于是有人以为它们完全等价。为什么混因为最终指向的类描述确实相同但获取方式和触发时机不同。区分方法看手里有什么、想什么时候确定。Class.forName靠字符串类名可以在运行时拼出来适合配置驱动但字符串写错编译期发现不了DemoBean.class在编译期就绑定了类写法安全但不适合类名来自外部配置的场景getClass()要求先有对象适合从实例反查类型包装类的TYPE拿到的是基本类型描述不是包装类描述。入口不同决定了代码的灵活性和安全检查的时机。5.5 getDeclared 系列和 get 系列范围不一样易混点getDeclaredMethods()和getMethods()都叫“获取方法”有人以为只是名字长短不同。为什么混因为两个方法都返回方法数组打印出来都像那么回事。区别在范围getDeclaredMethods()取本类声明的方法包含私有方法不包含继承来的方法getMethods()取公开方法包含继承来的公开方法。区分方法看你要找谁。要扫描本类所有成员包括私有实现用getDeclared系列要按公开 API 调用用不带Declared的系列。找单个方法时还要传参数类型比如getDeclaredMethod(sayHello, String.class)只写方法名不写参数类型遇到重载方法就会失败。面试题里问“怎么获取所有方法”先确认“所有”是本类所有还是公开可访问的所有不然答案会差一截。6. 什么时候该用反射、什么时候别用边界判断与一个具体例子假设你在做一个通用配置加载器配置文件里写着某个实现类的全路径程序启动时读到字符串用Class.forName找到类再用Constructor.newInstance()创建对象。这个场景里代码在编译期确实不知道具体类配置换一个实现就能切换反射是合适的。它把“选哪个类”从代码里挪到了配置里代价是类名写错要到运行时才发现所以配置加载器要给出清楚的错误提示。反过来普通业务里已经知道要创建DemoBean却为了“以后可能扩展”写成反射通常不划算。编译器帮你检查类型、检查构造方法、检查方法名这些好处都被绕过去了换来的灵活性可能根本用不上。高频调用的路径更要谨慎每次请求都Class.forName再查找方法性能和维护成本都会上来。场景更适合的做法判断依据类名来自配置运行时才确定反射编译期无法写死类型框架扫描类、统一处理成员反射需要处理未知类普通业务创建已知对象new编译期类型明确高频热路径调用直接调用或缓存反射结果性能敏感查找成本会被放大想改另一个类的私有状态先改设计慎用反射能访问不等于该访问我踩过的一次坑是早期写工具类时为了“通用”把每次调用都写成反射查找方法。功能跑得通数据量一上来就开始慢后来把Method和Constructor缓存起来才缓过来。从那以后我每次想用反射前都强制先问一句这个类名能不能在编译期写死能写死就别绕路绕不过去再把查找结果缓存好把异常提示写清楚。希望帮到你。本文还有配套的精品资源点击获取 SEO 优化官网定制响应式建站教育培训建站