目录
[1. 泛型本质与泛型擦除原理](#1. 泛型本质与泛型擦除原理)
[2. 通配符上下界与 PECS 原则](#2. 通配符上下界与 PECS 原则)
[3. 泛型的使用限制](#3. 泛型的使用限制)
[1. Class 对象的 3 种获取方式](#1. Class 对象的 3 种获取方式)
[2. 反射核心 API 与使用](#2. 反射核心 API 与使用)
[3. 反射的优缺点与性能优化](#3. 反射的优缺点与性能优化)
[1. 四大元注解与注解本质](#1. 四大元注解与注解本质)
[2. 自定义注解的实现与两种解析方式](#2. 自定义注解的实现与两种解析方式)
[3. Spring 中反射 + 注解的典型应用场景](#3. Spring 中反射 + 注解的典型应用场景)
[1. 你的项目中 MyBatis-Plus 泛型 BaseMapper 的实现逻辑](#1. 你的项目中 MyBatis-Plus 泛型 BaseMapper 的实现逻辑)
[2. 你的项目自定义注解的实现与解析](#2. 你的项目自定义注解的实现与解析)
[3. Spring 中反射 + 注解的 3 个典型应用场景](#3. Spring 中反射 + 注解的 3 个典型应用场景)
[Day1 当日验收](#Day1 当日验收)
[泛型、反射、注解 面试模拟题(实习面试专属,附标准答案)](#泛型、反射、注解 面试模拟题(实习面试专属,附标准答案))
[一、基础必考题(8 道,中小厂实习 100% 覆盖)](#一、基础必考题(8 道,中小厂实习 100% 覆盖))
[1. 什么是泛型擦除?泛型擦除带来了哪些问题?怎么规避?](#1. 什么是泛型擦除?泛型擦除带来了哪些问题?怎么规避?)
[2. ? extends T和? super T的核心区别是什么?PECS 原则是什么?](#2. ? extends T和? super T的核心区别是什么?PECS 原则是什么?)
[3. Class 对象的三种获取方式是什么?各自的区别与适用场景?](#3. Class 对象的三种获取方式是什么?各自的区别与适用场景?)
[4. 反射的核心 API 有哪些?怎么突破 private 权限的限制?](#4. 反射的核心 API 有哪些?怎么突破 private 权限的限制?)
[5. 反射的优缺点是什么?怎么优化反射的性能?](#5. 反射的优缺点是什么?怎么优化反射的性能?)
[6. 四大元注解的作用是什么?@Retention 的三个生命周期的区别?](#6. 四大元注解的作用是什么?@Retention 的三个生命周期的区别?)
[7. @Override 注解的底层原理是什么?](#7. @Override 注解的底层原理是什么?)
[8. 为什么 Spring、MyBatis 等框架大量使用反射?](#8. 为什么 Spring、MyBatis 等框架大量使用反射?)
[二、场景坑点题(6 道,大厂实习高频业务题)](#二、场景坑点题(6 道,大厂实习高频业务题))
[1. 你的校园二手平台用了 MyBatis-Plus 的泛型 BaseMapper,GoodsMapper extends BaseMapper ,底层是怎么知道泛型的实际类型是 Goods 的?](#1. 你的校园二手平台用了 MyBatis-Plus 的泛型 BaseMapper,GoodsMapper extends BaseMapper ,底层是怎么知道泛型的实际类型是 Goods 的?)
[2. 你的校园二手平台自定义了 @RequiresPermission 权限注解,怎么实现?怎么解析注解做权限校验?](#2. 你的校园二手平台自定义了 @RequiresPermission 权限注解,怎么实现?怎么解析注解做权限校验?)
[3. 以下代码编译会报错吗?为什么?怎么修复?](#3. 以下代码编译会报错吗?为什么?怎么修复?)
[4. 你的校园二手平台用反射实现了 Excel 导入导出工具,性能很差,怎么优化?](#4. 你的校园二手平台用反射实现了 Excel 导入导出工具,性能很差,怎么优化?)
[5. 以下代码运行会报错吗?为什么?](#5. 以下代码运行会报错吗?为什么?)
[6. 你的校园二手平台用 Spring AOP 实现了 @OperationLog 操作日志注解,怎么实现?](#6. 你的校园二手平台用 Spring AOP 实现了 @OperationLog 操作日志注解,怎么实现?)
[三、进阶原理题(4 道,中大厂实习拔高题)](#三、进阶原理题(4 道,中大厂实习拔高题))
[1. @Autowired 注解的底层实现原理是什么?](#1. @Autowired 注解的底层实现原理是什么?)
[2. 为什么不能创建泛型数组?](#2. 为什么不能创建泛型数组?)
[3. 反射调用方法和直接调用方法,性能差距有多大?为什么?](#3. 反射调用方法和直接调用方法,性能差距有多大?为什么?)
[4. 注解的本质是什么?注解是怎么被解析的?](#4. 注解的本质是什么?注解是怎么被解析的?)
模块一:泛型核心原理与应用
1. 泛型本质与泛型擦除原理
核心定义
- 泛型本质 :参数化类型,把类型作为参数传递,编译期做类型安全检查,避免运行期强制类型转换的
ClassCastException,实现代码复用。 - 泛型擦除(面试 100% 问) :泛型是 Java 的编译期语法糖 ,编译完成后所有泛型信息会被擦除:
- 无边界泛型(
<T>):擦除后替换为Object; - 有上界的泛型(
<T extends Number>):擦除后替换为上界类型Number; - 编译期会自动插入强制类型转换,保证运行期类型正确;
- 运行期无泛型信息:
List<String>和List<Integer>的 Class 对象完全相同。
- 无边界泛型(
- 擦除带来的问题:运行期无法获取泛型类型、基本类型不能作为泛型参数、泛型方法签名冲突、强制类型转换风险。
- 规避方案:通过反射获取泛型的实际类型、用包装类替代基本类型、用通配符灵活扩展类型。
个人理解
泛型的核心价值是编译期类型安全,把运行期的类型错误提前到编译期暴露,同时实现通用代码的复用;泛型擦除是为了兼容 JDK1.5 之前的老版本,属于历史设计的妥协,也是面试的核心考点。
项目实际使用场景
结合校园二手平台开发实践:
- MyBatis-Plus 泛型 BaseMapper :项目中
GoodsMapper extends BaseMapper<Goods>、OrderMapper extends BaseMapper<Order>,MP 底层通过反射获取 Mapper 接口的泛型实际类型,自动识别实体类,生成对应表的 CRUD SQL,不需要每个 Mapper 重复写增删改查; - 通用返回封装 :项目全局统一返回类
Result<T>,用泛型封装不同接口的返回数据类型(商品 VO、订单 VO、用户 VO),编译期检查类型,避免强制转换异常; - 通用业务层封装 :自定义
BaseService<T>泛型基类,封装通用的增删改查方法,GoodsService、OrderService直接继承复用,不需要重复实现基础逻辑。
面试考点标注
✅ 必问:什么是泛型擦除?擦除带来了什么问题?怎么规避?
✅ 细节:List<String>和List<Integer>的 Class 对象是否相同?为什么?
✅ 场景题:泛型的核心好处是什么?为什么要设计泛型擦除?
2. 通配符上下界与 PECS 原则
核心定义
泛型通配符用来解决泛型的协变 / 逆变问题,实现类型的灵活扩展,分为三类:
| 通配符 | 定义 | 特性 | 核心规则 |
|---|---|---|---|
? |
无界通配符 | 接收任意类型,只能读,不能写(除了 null) | 适配所有未知类型的场景 |
? extends T |
上界通配符 | 只能是 T 或 T 的子类,只能读,不能写(除了 null) | 生产者(读取数据)用 extends(PECS:Producer Extends) |
? super T |
下界通配符 | 只能是 T 或 T 的父类,只能写,读出来是 Object | 消费者(写入数据)用 super(PECS:Consumer Super) |
个人理解
PECS 是通配符使用的黄金原则,核心是读写分离:只需要读取数据用 extends,只需要写入数据用 super,既读又写不用通配符。
项目实际使用场景
- 通用实体工具类 :项目的实体字段校验工具,方法参数用
List<? extends BaseEntity>,接收所有继承了BaseEntity的实体子类(Goods、Order、User),统一读取id、createTime等通用字段做校验; - 通用集合添加方法 :批量添加商品的工具方法,参数用
List<? super Goods>,支持List<Object>、List<BaseEntity>等父类集合添加 Goods 对象,提升灵活性; - MP 通用查询封装:MyBatis-Plus 的 Lambda 查询 Wrapper 底层用了通配符,支持不同实体类的通用查询逻辑。
面试考点标注
✅ 必问:? extends T和? super T的核心区别是什么?PECS 原则是什么?
✅ 场景题:什么时候用上界通配符?什么时候用下界通配符?
✅ 坑点:为什么? extends T的集合不能写入元素(除了 null)?
3. 泛型的使用限制
核心定义
泛型擦除带来的编译期限制,核心限制有 5 个:
- 不能用基本类型作为泛型参数,只能用包装类(擦除后是 Object,基本类型不是 Object 的子类);
- 不能对泛型类型使用
instanceof,运行期泛型信息已擦除,无法判断; - 不能
new泛型数组,擦除后是 Object 数组,类型不安全,会出现类型转换异常; - 静态方法、静态变量不能使用泛型类型参数,静态内容优先于对象初始化,泛型属于对象的类型参数;
- 泛型类不能继承
Throwable,不能 catch 泛型异常,异常是运行期抛出的,擦除后无法区分泛型异常。
个人理解
所有泛型的限制本质都是泛型擦除导致运行期无类型信息,编译期无法做类型检查,因此直接禁止这些操作,避免运行期异常。
项目实际使用场景
- 项目中所有泛型集合都用包装类:
List<Long> goodsIdList,不用List<long>,符合泛型规范; - 避免创建泛型数组,用
ArrayList<T>替代数组,保证类型安全; - 自定义业务异常不使用泛型,直接继承
RuntimeException,避免异常捕获的问题。
面试考点标注
✅ 高频:泛型有哪些核心使用限制?
✅ 原理:为什么不能用基本类型作为泛型参数?为什么不能 new 泛型数组?
模块二:反射核心原理与应用
1. Class 对象的 3 种获取方式
核心定义
Class是类的元数据对象,JVM 中每个类有且只有一个Class对象,存储类的所有结构信息,是反射的入口,三种获取方式核心区别:
| 获取方式 | 执行时机 | 类是否初始化 | 适用场景 |
|---|---|---|---|
类名.class |
编译期 | 不执行类的初始化,不执行静态代码块 | 工具类、已知类名的场景 |
对象.getClass() |
运行期 | 类已初始化 | 已有实例对象,获取对象的实际类型 |
Class.forName("全类名") |
运行期 | 会加载类,执行静态代码块 | 动态加载未知类,框架启动加载类、JDBC 加载驱动 |
个人理解
三种方式的核心差异是类的初始化时机 ,框架大多用Class.forName实现动态加载,业务工具用类名.class,实例对象用getClass()。
项目实际使用场景
- Excel 导入导出工具 :项目的 Excel 工具类,通过
Goods.class获取实体的 Class 对象,反射读取字段和注解,自动映射 Excel 列,不需要每个实体单独写导入逻辑; - Spring IOC 容器启动 :Spring 启动时通过
Class.forName扫描并加载配置的类,动态创建 Bean 实例; - 自定义注解解析 :接口请求时,通过
handlerMethod.getMethod().getClass()获取方法的 Class 对象,反射读取方法上的权限、日志注解。
面试考点标注
✅ 必问:Class 对象的三种获取方式是什么?各自的区别与适用场景?
✅ 细节:Class.forName()会不会执行类的静态代码块?
2. 反射核心 API 与使用
核心定义
反射是 Java 的动态能力,运行期可以获取类的所有信息,动态创建对象、调用方法、修改属性,是所有框架的底层核心,四大核心 API:
- Constructor(构造器) :
getDeclaredConstructors()获取所有构造器,newInstance()动态创建对象; - Field(字段) :
getDeclaredFields()获取所有字段,get()/set()读写字段值,setAccessible(true)突破 private 权限限制; - Method(方法) :
getDeclaredMethods()获取所有方法,invoke()动态调用方法,支持 private 方法; - 注解 API :
getAnnotation()获取类 / 方法 / 字段上的注解。
个人理解
反射的核心是突破编译期的限制,可以拿到类的所有私有信息,实现通用化的逻辑,是框架解耦的核心基础;业务开发中尽量不要手动写反射,但必须理解原理,才能读懂框架源码、排查框架问题。
项目实际使用场景
- MyBatis 结果映射:MyBatis 查询数据库后,通过反射调用实体类的 setter 方法,给字段赋值,把 ResultSet 映射为 Java 实体;
- Spring 依赖注入 :
@Autowired注解的底层,通过反射给 private 字段赋值,不需要写 setter 方法; - Excel 工具类:项目的批量商品导入工具,反射读取 Excel 行数据,动态给 Goods 实体的字段赋值,实现通用导入。
面试考点标注
✅ 必问:反射的核心 API 有哪些?怎么突破 private 权限的限制?
✅ 场景题:怎么通过反射调用 private 方法、修改 private 字段?
3. 反射的优缺点与性能优化
核心定义
(1)优缺点
- 优点:极度灵活,运行期动态扩展,实现通用化功能,解耦业务代码和通用逻辑,是所有框架的核心基础;
- 缺点:① 性能低,比直接调用慢几十到上百倍;② 破坏封装,突破 private 权限,破坏代码安全性;③ 跳过编译期检查,容易出现运行期异常。
(2)性能优化方案
- 缓存元数据 :缓存
Class、Method、Field对象,避免每次反射都重新获取,是最有效的优化手段; - 关闭安全检查 :
setAccessible(true)不仅能突破权限,还能跳过 JVM 的安全检查,性能提升数倍; - 字节码框架替代:用 ASM、CGLIB 等字节码框架,直接生成字节码,比反射性能高很多。
个人理解
反射的性能问题在框架中通过缓存已经被优化到几乎可以忽略,业务开发中不要滥用反射,框架用反射是为了实现通用解耦,是性价比极高的设计。
项目实际使用场景
- Excel 工具类优化 :项目的 Excel 导入工具,缓存了所有实体的
Field、注解信息,不需要每次导入都重新反射获取,性能提升 10 倍以上; - 注解解析缓存:自定义的权限、日志注解,解析后缓存到本地,不需要每次接口请求都反射解析,提升接口响应速度;
- Spring 框架优化:Spring 的 Bean 元数据、方法、字段都做了缓存,不会重复反射,性能损耗极小。
面试考点标注
✅ 必问:反射的优缺点是什么?怎么优化反射的性能?
✅ 必问:为什么 Spring、MyBatis 等框架大量使用反射?
标准答案:为了实现通用解耦,不需要开发者编写重复的硬编码。比如 MyBatis 不需要每个实体写结果映射代码,Spring 不需要每个 Bean 写依赖注入代码,反射是唯一能实现「一次编写,适配所有场景」的方式,是框架的核心灵魂。
模块三:注解核心原理与应用
1. 四大元注解与注解本质
核心定义
- 注解本质:代码的元数据(标签),本身不影响代码逻辑,只有被解析后才会生效,是代码和逻辑解耦的核心手段。
- 四大元注解(给注解打标记的注解,自定义注解必用) :
@Target:指定注解的作用位置:TYPE(类 / 接口)、METHOD(方法)、FIELD(字段)、PARAMETER(参数)等;@Retention:指定注解的生命周期,自定义业务注解必须设为RUNTIME:SOURCE:编译期保留,编译后丢弃,比如@Override,编译期做语法检查;CLASS:编译到 class 文件,运行期丢弃,默认值;RUNTIME:运行期保留,可被反射读取,所有业务自定义注解必须用这个;
@Documented:注解会生成到 JavaDoc 中;@Inherited:注解可以被子类继承。
个人理解
注解就是给代码打标记,元注解就是定义标记的规则;生命周期是核心,自定义业务注解必须是RUNTIME,否则反射无法读取,注解就没有意义。
项目实际使用场景
- @OperationLog 操作日志注解 :
@Target(METHOD)、@Retention(RUNTIME),标记需要记录操作日志的接口; - @RequiresPermission 权限注解 :
@Target(METHOD)、@Retention(RUNTIME),标记接口需要的权限标识; - @ExcelColumn 列映射注解 :
@Target(FIELD)、@Retention(RUNTIME),标记实体字段对应的 Excel 列名,用于导入导出。
面试考点标注
✅ 必问:四大元注解的作用是什么?@Retention 的三个生命周期的区别?
✅ 必问:@Override的底层原理是什么?
标准答案:
@Override是SOURCE生命周期的注解,编译期检查方法是否正确重写了父类 / 接口的方法,不符合规则编译报错,编译后注解就被丢弃,运行期不存在。
2. 自定义注解的实现与两种解析方式
核心定义
(1)自定义注解实现步骤
- 用
@interface定义注解; - 用元注解标记注解的作用位置和生命周期;
- 定义注解的属性;
- 解析注解,实现业务逻辑。
(2)两种解析方式
| 解析方式 | 实现原理 | 适用场景 |
|---|---|---|
| 反射解析 | 运行期通过反射获取类 / 方法 / 字段上的注解,执行逻辑 | 通用工具类、拦截器、简单场景 |
| AOP 切面解析 | 结合 Spring AOP,拦截标注了注解的方法 / 类,执行切面逻辑 | 接口日志、权限校验、事务管理等 Spring 项目场景 |
个人理解
注解 + 反射 / AOP 是企业级项目通用功能的标准实现,把日志、权限、事务等通用逻辑和业务代码完全解耦,不需要每个业务方法重复写通用代码。
项目实际使用场景
- @RequiresPermission 权限注解:拦截器 + 反射解析,请求接口时,通过反射获取方法上的注解,校验用户是否有对应权限,无权限则返回 403;
- @OperationLog 日志注解:Spring AOP 环绕通知解析,拦截标注了注解的接口,自动记录操作人、请求参数、接口耗时、返回结果,保存到日志表;
- @ExcelColumn 注解:Excel 工具类反射解析,获取字段对应的列名,自动实现 Excel 和实体字段的映射。
面试考点标注
✅ 必问:自定义注解的实现步骤是什么?两种解析方式的区别与适用场景?
✅ 必问:@Autowired注解的实现逻辑是什么?
标准答案:Spring 在 Bean 生命周期的「属性填充」阶段,通过反射获取字段 / 方法上的
@Autowired注解,根据类型 / 名称从 IOC 容器中找到对应的 Bean,反射给字段赋值,不需要开发者写 setter 方法。
3. Spring 中反射 + 注解的典型应用场景
核心定义
Spring 全家桶的核心设计就是「注解 + 反射 + AOP」,三个最典型的应用:
- IOC 依赖注入 :
@Autowired、@Value、@Bean等注解,反射解析后实现 Bean 的创建和属性注入; - AOP 切面编程 :
@Aspect、@Around、@Before等注解,反射解析后实现切面逻辑; - 事务管理 :
@Transactional注解,AOP + 反射解析,实现事务的开启、提交、回滚。
项目实际使用场景
- 项目订单模块的
@Transactional注解,Spring AOP 解析后,实现下单扣库存、扣余额的事务控制; - Controller 层的
@RestController、@RequestMapping注解,SpringMVC 启动时反射扫描,注册接口路由; - 配置类的
@Configuration、@Bean注解,Spring 反射解析,把 Bean 注册到 IOC 容器中。
面试考点标注
✅ 高频:Spring 框架中反射 + 注解的典型应用场景有哪些?
✅ 细节:@Transactional注解的底层实现原理。
项目绑定专项巩固(面试加分核心)
1. 你的项目中 MyBatis-Plus 泛型 BaseMapper 的实现逻辑
你的项目中GoodsMapper extends BaseMapper<Goods>,底层完整实现:
BaseMapper<T>是泛型接口,定义了通用的 CRUD 方法(insert、delete、update、select);- MP 启动时,通过反射获取 Mapper 接口的泛型实际类型(比如
Goods),拿到实体类的 Class 对象; - 反射读取实体类的
@TableName、@TableId、@TableField等注解,获取表名、主键、字段名; - 通过 JDK 动态代理生成 Mapper 接口的代理对象,执行方法时自动拼接对应表的 SQL,不需要开发者写 XML 和 SQL。
2. 你的项目自定义注解的实现与解析
(1)@RequiresPermission 权限注解
- 定义:
@Target(METHOD) @Retention(RUNTIME),属性value为权限标识; - 解析:自定义拦截器,请求接口时,通过
HandlerMethod获取接口方法,反射读取方法上的注解,校验用户的权限列表是否包含注解的权限标识,不包含则返回 403。
(2)@OperationLog 操作日志注解
- 定义:
@Target(METHOD) @Retention(RUNTIME),属性value为操作描述; - 解析:Spring AOP 环绕通知,拦截标注了注解的方法,反射获取注解的描述,记录请求参数、接口耗时、返回结果、操作人 ID,异步保存到操作日志表。
3. Spring 中反射 + 注解的 3 个典型应用场景
@Autowired依赖注入:Bean 生命周期属性填充阶段,反射获取字段上的注解,从容器中获取 Bean,反射给字段赋值;@RequestMapping接口注册:SpringMVC 启动时,反射扫描所有 Controller 的方法,获取注解的路由,注册到HandlerMapping;@Transactional事务管理:AOP 扫描标注了注解的方法,生成动态代理,执行方法前后开启、提交、回滚事务。
Day1 当日验收
带来的问题
规避方案
【考点对应】模块一:泛型擦除原理
2. ? extends T和? super T的核心区别是什么?PECS 原则是什么?
【标准答案】
核心区别
表格
| 通配符 | 定义 | 读写特性 | 适用场景 |
|---|---|---|---|
? extends T |
上界通配符,只能是 T 或 T 的子类 | 只能读,不能写(除了 null) | 生产者场景,只读取数据 |
? super T |
下界通配符,只能是 T 或 T 的父类 | 只能写,读出来是 Object | 消费者场景,只写入数据 |
PECS 原则
Producer Extends, Consumer Super:
【考点对应】模块一:通配符上下界与 PECS 原则
3. Class 对象的三种获取方式是什么?各自的区别与适用场景?
【标准答案】
表格
| 获取方式 | 执行时机 | 类是否初始化 | 适用场景 |
|---|---|---|---|
类名.class |
编译期 | 不执行类的初始化,不执行静态代码块 | 工具类、已知类名的场景,性能最高 |
对象.getClass() |
运行期 | 类已初始化完成 | 已有实例对象,获取对象的实际运行时类型 |
Class.forName("全类名") |
运行期 | 会加载类并执行静态代码块 | 动态加载未知类,框架启动加载类、JDBC 加载驱动 |
【考点对应】模块二:Class 对象获取方式
4. 反射的核心 API 有哪些?怎么突破 private 权限的限制?
【标准答案】
四大核心 API
突破 private 权限
调用setAccessible(true)方法,关闭 JVM 的安全检查,不仅可以访问 private 成员,还能大幅提升反射性能。示例:
java
运行
Field field = Goods.class.getDeclaredField("price");
field.setAccessible(true); // 突破private权限
field.set(goods, 100); // 修改private字段
【考点对应】模块二:反射核心 API
5. 反射的优缺点是什么?怎么优化反射的性能?
【标准答案】
优缺点
性能优化方案
【考点对应】模块二:反射优缺点与性能优化
6. 四大元注解的作用是什么?@Retention 的三个生命周期的区别?
【标准答案】
四大元注解
元注解是给注解打标记的注解,自定义注解必用:
@Retention 三个生命周期
表格
| 生命周期 | 保留阶段 | 适用场景 | 示例 |
|---|---|---|---|
SOURCE |
编译期保留,编译后丢弃 | 编译期语法检查 | @Override、@SuppressWarnings |
CLASS |
编译到 class 文件,运行期丢弃 | 字节码级别的工具 | 默认值 |
RUNTIME |
运行期保留,可被反射读取 | 所有业务自定义注解 | @Autowired、@Transactional |
【考点对应】模块三:元注解核心特性
7. @Override 注解的底层原理是什么?
【标准答案】@Override是SOURCE生命周期的注解,只在编译期生效:
核心作用是编译期语法检查,避免开发者写错重写方法的签名。
【考点对应】模块三:注解生命周期与原理
8. 为什么 Spring、MyBatis 等框架大量使用反射?
【标准答案】核心目的是实现通用解耦,避免重复硬编码:
【考点对应】模块二:反射框架应用
二、场景坑点题(6 道,大厂实习高频业务题)
1. 你的校园二手平台用了 MyBatis-Plus 的泛型 BaseMapper,GoodsMapper extends BaseMapper<Goods>,底层是怎么知道泛型的实际类型是 Goods 的?
【标准答案】MyBatis-Plus 通过反射获取泛型的实际类型,完整流程:
核心代码示例:
java
运行
Class<?> mapperInterface = GoodsMapper.class;
ParameterizedType parameterizedType = (ParameterizedType) mapperInterface.getGenericInterfaces()[0];
Class<?> entityClass = (Class<?>) parameterizedType.getActualTypeArguments()[0];
// entityClass就是Goods.class
【考点对应】模块一:泛型擦除的规避方案、反射获取泛型类型
2. 你的校园二手平台自定义了 @RequiresPermission 权限注解,怎么实现?怎么解析注解做权限校验?
【标准答案】
步骤 1:自定义注解
java
运行
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresPermission {
String value(); // 权限标识
}
步骤 2:拦截器 + 反射解析注解
java
运行
@Component
public class PermissionInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 只拦截Controller方法
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 反射获取方法上的注解
RequiresPermission annotation = handlerMethod.getMethod().getAnnotation(RequiresPermission.class);
if (annotation == null) {
return true; // 不需要权限
}
// 获取用户的权限列表
List<String> permissions = getCurrentUserPermissions();
// 校验权限
if (!permissions.contains(annotation.value())) {
throw new PermissionException("无权限访问");
}
return true;
}
}
步骤 3:注册拦截器
java
运行
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new PermissionInterceptor()).addPathPatterns("/**");
}
}
步骤 4:接口上使用注解
java
运行
@PostMapping("/goods/add")
@RequiresPermission("goods:add")
public Result<Void> addGoods(@RequestBody GoodsAddDTO dto) {
goodsService.add(dto);
return Result.ok();
}
【考点对应】模块三:自定义注解实现与反射解析
3. 以下代码编译会报错吗?为什么?怎么修复?
java
运行
public class GenericTest {
public void method(List<String> list) {}
public void method(List<Integer> list) {}
}
【标准答案】编译报错。原因:泛型擦除后,两个方法的签名完全相同,都是method(List list),JVM 无法区分,因此不能重载。修复方案:修改方法名,或者添加不同的参数,避免擦除后签名冲突。
【考点对应】模块一:泛型擦除带来的问题
4. 你的校园二手平台用反射实现了 Excel 导入导出工具,性能很差,怎么优化?
【标准答案】核心优化方案:
【考点对应】模块二:反射性能优化
5. 以下代码运行会报错吗?为什么?
java
运行
List<String> list = new ArrayList<>();
list.add("test");
List list2 = list;
list2.add(123);
System.out.println(list.get(0));
System.out.println(list.get(1));
【标准答案】运行不会报错,输出:
plaintext
test
123
原因:泛型擦除后,List<String>变成List,可以添加任意类型的对象;编译期会给get()方法自动插入强制类型转换,这里list.get(1)会自动转为String,但 123 是 Integer,运行时会抛出ClassCastException。
这就是泛型擦除带来的运行期类型安全问题,也是为什么要在编译期做泛型检查的原因。
【考点对应】模块一:泛型擦除的运行期风险
6. 你的校园二手平台用 Spring AOP 实现了 @OperationLog 操作日志注解,怎么实现?
【标准答案】
步骤 1:自定义注解
java
运行
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OperationLog {
String value(); // 操作描述
}
步骤 2:AOP 切面解析注解
java
运行
@Aspect
@Component
public class OperationLogAspect {
@Around("@annotation(operationLog)")
public Object around(ProceedingJoinPoint point, OperationLog operationLog) throws Throwable {
// 反射获取注解的操作描述
String desc = operationLog.value();
// 记录请求参数、操作人、开始时间
long start = System.currentTimeMillis();
Object result = point.proceed();
long time = System.currentTimeMillis() - start;
// 异步保存日志到数据库
saveLog(desc, point, time, result);
return result;
}
}
步骤 3:接口上使用注解
java
运行
@PostMapping("/goods/delete")
@OperationLog("删除商品")
public Result<Void> deleteGoods(@RequestParam Long id) {
goodsService.delete(id);
return Result.ok();
}
【考点对应】模块三:注解 + AOP 解析方式
三、进阶原理题(4 道,中大厂实习拔高题)
1. @Autowired 注解的底层实现原理是什么?
【标准答案】@Autowired的底层是注解 + 反射 + Bean 生命周期钩子,完整流程:
【考点对应】模块三:Spring 注解底层原理
2. 为什么不能创建泛型数组?
【标准答案】核心原因是泛型擦除导致类型不安全:
替代方案:用ArrayList<T>替代泛型数组,ArrayList底层是Object[],但通过泛型编译期检查保证类型安全。
【考点对应】模块一:泛型使用限制
3. 反射调用方法和直接调用方法,性能差距有多大?为什么?
【标准答案】未优化的反射调用比直接调用慢50-100 倍 ,优化后(缓存元数据 + 关闭安全检查)差距缩小到1-2 倍,生产环境可以接受。
性能差的原因
优化后性能提升的原因
【考点对应】模块二:反射性能原理
4. 注解的本质是什么?注解是怎么被解析的?
【标准答案】
注解的本质
注解本质是一个继承了Annotation接口的接口 ,编译期会自动生成注解的实现类,运行期通过动态代理生成实现类的实例。比如自定义@OperationLog注解,编译后会生成:
java
public interface OperationLog extends Annotation {
String value();
}
运行期通过动态代理生成实现类,注解的属性值存储在实现类的成员变量中。
注解的两种解析方式
【考点对应】模块三:注解本质与解析原理
实习面试答题加分技巧(绑定你的校园二手平台项目)
-
能手写自定义注解 + 反射解析的完整代码(比如自定义 @Log 注解,反射获取方法上的注解并打印日志);
-
能口述泛型擦除的完整流程,以及擦除带来的问题与规避方案;
-
能说出 Spring、MyBatis 框架中 3 个以上反射 + 注解的应用场景;
-
能讲清 PECS 原则,以及通配符上下界的使用场景。
泛型、反射、注解 面试模拟题(实习面试专属,附标准答案)
一、基础必考题(8 道,中小厂实习 100% 覆盖)
1. 什么是泛型擦除?泛型擦除带来了哪些问题?怎么规避?
【标准答案】
泛型擦除定义
泛型是 Java 的编译期语法糖,编译完成后所有泛型信息会被擦除:
-
无边界泛型
<T>擦除后替换为Object; -
有上界的泛型
<T extends Number>擦除后替换为上界类型Number; -
编译期自动插入强制类型转换,保证运行期类型正确;
-
运行期无泛型信息,
List<String>和List<Integer>的 Class 对象完全相同。 -
运行期无法获取泛型的实际类型;
-
基本类型不能作为泛型参数,只能用包装类;
-
泛型方法签名冲突(擦除后方法签名相同,无法重载);
-
无法对泛型类型使用
instanceof判断; -
不能创建泛型数组,类型不安全。
-
通过反射获取泛型的实际类型(
ParameterizedType); -
用包装类替代基本类型,或使用专门的基本类型集合库(Trove、FastUtil);
-
避免擦除后方法签名冲突,修改方法名;
-
用
ArrayList<T>替代泛型数组,保证类型安全。 -
生产者(只读取数据,不写入)用
? extends T; -
消费者(只写入数据,不读取)用
? super T; -
既读又写的场景,不要用通配符。
-
Class:类的元数据对象,获取类的所有结构信息; -
Constructor:构造器,newInstance()动态创建对象; -
Field:字段,get()/set()读写字段值; -
Method:方法,invoke()动态调用方法。 -
优点:极度灵活,运行期动态扩展,实现通用化功能,是所有框架的核心基础,解耦业务代码和通用逻辑;
-
缺点:① 性能低,比直接调用慢几十到上百倍;② 破坏封装,突破 private 权限,降低代码安全性;③ 跳过编译期检查,容易出现运行期异常。
-
缓存元数据 :缓存
Class、Method、Field对象,避免每次反射都重新获取,是最有效的优化手段; -
关闭安全检查 :
setAccessible(true)跳过 JVM 安全检查,性能提升数倍; -
字节码框架替代:用 ASM、CGLIB 等字节码框架直接生成字节码,性能接近直接调用。
-
@Target:指定注解的作用位置(类、方法、字段、参数等); -
@Retention:指定注解的生命周期; -
@Documented:注解会生成到 JavaDoc 中; -
@Inherited:注解可以被子类继承。 -
编译期,编译器扫描所有标注了
@Override的方法; -
检查该方法是否正确重写了父类 / 接口的方法(方法名、参数列表、返回值必须一致);
-
如果不符合重写规则,直接编译报错;
-
编译完成后,
@Override注解会被擦除,运行期不存在。 -
框架需要适配所有开发者的业务代码,无法提前知道开发者会写什么类、什么方法;
-
反射可以在运行期动态获取类的信息,动态创建对象、调用方法,实现「一次编写,适配所有场景」;
-
结合注解,可以让开发者通过简单的注解配置,替代大量重复的硬编码;
-
比如 MyBatis 不需要每个实体写结果映射代码,Spring 不需要每个 Bean 写依赖注入代码,都是基于反射实现的。
-
MP 启动时,扫描所有继承了
BaseMapper的 Mapper 接口; -
通过
mapperInterface.getGenericInterfaces()获取接口的泛型父接口BaseMapper<Goods>; -
强制转换为
ParameterizedType,调用getActualTypeArguments()[0]获取泛型的实际类型Goods.class; -
反射读取
Goods类上的@TableName、@TableId等注解,获取表名、主键、字段信息; -
通过 JDK 动态代理生成 Mapper 接口的代理对象,执行方法时自动拼接对应表的 SQL。
-
缓存元数据 :第一次导入导出时,反射获取实体的
Field、注解信息,缓存到本地 Map,后续直接从缓存取,避免每次都反射获取,性能提升 10 倍以上; -
关闭安全检查 :所有
Field调用setAccessible(true),跳过 JVM 安全检查,性能提升数倍; -
批量操作:一次性读取所有 Excel 行,批量反射赋值,不要逐行反射;
-
使用高性能 Excel 库:用 Alibaba EasyExcel 替代 POI,EasyExcel 底层做了大量反射优化和内存优化,性能更高;
-
避免反射调用方法:直接给字段赋值,不要调用 setter 方法,减少反射调用次数。
-
Spring 启动时,扫描所有 Bean,通过反射获取类的字段、方法上的
@Autowired注解; -
Bean 实例化完成后,在
populateBean(属性填充)阶段,处理@Autowired注解; -
对于标注了
@Autowired的字段:- 根据字段的类型,从 Spring IOC 容器中找到对应的 Bean;
- 调用
field.setAccessible(true)突破 private 权限; - 通过反射
field.set(bean, targetBean)给字段赋值;
-
对于标注了
@Autowired的 setter 方法:- 根据方法参数的类型找到对应的 Bean;
- 通过反射
method.invoke(bean, targetBean)调用 setter 方法赋值;
-
如果找不到对应的 Bean,或者找到多个,抛出异常。
-
泛型擦除后,
T[]变成Object[],可以添加任意类型的对象; -
数组是协变的,
String[]是Object[]的子类,而泛型不是协变的,List<String>不是List<Object>的子类; -
如果允许创建泛型数组,会出现以下问题:
javaList<String>[] listArray = new List<String>[10]; // 编译报错 Object[] objArray = listArray; objArray[0] = new ArrayList<Integer>(); // 编译通过 String s = listArray[0].get(0); // 运行期ClassCastException -
为了避免这种运行期类型安全问题,Java 直接禁止创建泛型数组。
-
方法查找开销:每次反射都要遍历类的方法列表,找到对应的 Method 对象;
-
安全检查开销:JVM 每次反射调用都要做权限检查、参数检查;
-
参数装箱拆箱:反射调用方法时,基本类型参数需要装箱为包装类,返回值需要拆箱;
-
无法内联优化:JIT 编译器无法对反射调用做内联优化,而直接调用可以内联,性能大幅提升。
-
缓存 Method 对象,避免每次查找方法;
-
setAccessible(true)关闭安全检查,跳过权限校验; -
提前做参数装箱拆箱,避免运行期自动装箱拆箱。
-
编译期解析 :编译器扫描注解,做语法检查或生成额外代码,比如
@Override、Lombok 的@Data; -
运行期解析 :通过反射获取类 / 方法 / 字段上的注解实例,读取属性值执行业务逻辑,比如
@Autowired、@Transactional。 -
所有问题都绑定项目实践 :
- 问泛型:主动说「我做校园二手平台的时候,用了 MyBatis-Plus 的泛型 BaseMapper,底层就是通过反射获取泛型的实际类型,自动生成 CRUD SQL,不用每个 Mapper 写重复代码」;
- 问注解:主动说「我自己实现了 @RequiresPermission 权限注解和 @OperationLog 日志注解,分别用拦截器和 AOP 解析,把通用逻辑和业务代码解耦,非常方便」;
- 问反射:主动说「我写的 Excel 导入导出工具就是用反射实现的,一开始性能很差,后来缓存了 Field 和注解信息,性能提升了 10 倍多」;
-
主动说踩坑经验:比如问泛型擦除,就说「我之前踩过泛型擦除导致的 ClassCastException 的坑,后来才理解泛型是编译期语法糖,运行期没有类型信息」;
-
答题逻辑固定为「结论→原理→我的项目实践 / 踩坑」,比单纯背知识点的候选人认可度高很多。