泛型、反射、注解(Spring 框架核心底层)专属复习笔记

目录

模块一:泛型核心原理与应用

[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 的编译期语法糖 ,编译完成后所有泛型信息会被擦除:
    1. 无边界泛型(<T>):擦除后替换为Object
    2. 有上界的泛型(<T extends Number>):擦除后替换为上界类型Number
    3. 编译期会自动插入强制类型转换,保证运行期类型正确;
    4. 运行期无泛型信息:List<String>List<Integer>的 Class 对象完全相同。
  • 擦除带来的问题:运行期无法获取泛型类型、基本类型不能作为泛型参数、泛型方法签名冲突、强制类型转换风险。
  • 规避方案:通过反射获取泛型的实际类型、用包装类替代基本类型、用通配符灵活扩展类型。
个人理解

泛型的核心价值是编译期类型安全,把运行期的类型错误提前到编译期暴露,同时实现通用代码的复用;泛型擦除是为了兼容 JDK1.5 之前的老版本,属于历史设计的妥协,也是面试的核心考点。

项目实际使用场景

结合校园二手平台开发实践:

  1. MyBatis-Plus 泛型 BaseMapper :项目中GoodsMapper extends BaseMapper<Goods>OrderMapper extends BaseMapper<Order>,MP 底层通过反射获取 Mapper 接口的泛型实际类型,自动识别实体类,生成对应表的 CRUD SQL,不需要每个 Mapper 重复写增删改查;
  2. 通用返回封装 :项目全局统一返回类Result<T>,用泛型封装不同接口的返回数据类型(商品 VO、订单 VO、用户 VO),编译期检查类型,避免强制转换异常;
  3. 通用业务层封装 :自定义BaseService<T>泛型基类,封装通用的增删改查方法,GoodsServiceOrderService直接继承复用,不需要重复实现基础逻辑。
面试考点标注

✅ 必问:什么是泛型擦除?擦除带来了什么问题?怎么规避?

✅ 细节: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,既读又写不用通配符。

项目实际使用场景
  1. 通用实体工具类 :项目的实体字段校验工具,方法参数用List<? extends BaseEntity>,接收所有继承了BaseEntity的实体子类(Goods、Order、User),统一读取idcreateTime等通用字段做校验;
  2. 通用集合添加方法 :批量添加商品的工具方法,参数用List<? super Goods>,支持List<Object>List<BaseEntity>等父类集合添加 Goods 对象,提升灵活性;
  3. MP 通用查询封装:MyBatis-Plus 的 Lambda 查询 Wrapper 底层用了通配符,支持不同实体类的通用查询逻辑。
面试考点标注

✅ 必问:? extends T? super T的核心区别是什么?PECS 原则是什么?

✅ 场景题:什么时候用上界通配符?什么时候用下界通配符?

✅ 坑点:为什么? extends T的集合不能写入元素(除了 null)?


3. 泛型的使用限制

核心定义

泛型擦除带来的编译期限制,核心限制有 5 个:

  1. 不能用基本类型作为泛型参数,只能用包装类(擦除后是 Object,基本类型不是 Object 的子类);
  2. 不能对泛型类型使用instanceof,运行期泛型信息已擦除,无法判断;
  3. 不能new泛型数组,擦除后是 Object 数组,类型不安全,会出现类型转换异常;
  4. 静态方法、静态变量不能使用泛型类型参数,静态内容优先于对象初始化,泛型属于对象的类型参数;
  5. 泛型类不能继承Throwable,不能 catch 泛型异常,异常是运行期抛出的,擦除后无法区分泛型异常。
个人理解

所有泛型的限制本质都是泛型擦除导致运行期无类型信息,编译期无法做类型检查,因此直接禁止这些操作,避免运行期异常。

项目实际使用场景
  1. 项目中所有泛型集合都用包装类:List<Long> goodsIdList,不用List<long>,符合泛型规范;
  2. 避免创建泛型数组,用ArrayList<T>替代数组,保证类型安全;
  3. 自定义业务异常不使用泛型,直接继承RuntimeException,避免异常捕获的问题。
面试考点标注

✅ 高频:泛型有哪些核心使用限制?

✅ 原理:为什么不能用基本类型作为泛型参数?为什么不能 new 泛型数组?


模块二:反射核心原理与应用

1. Class 对象的 3 种获取方式

核心定义

Class是类的元数据对象,JVM 中每个类有且只有一个Class对象,存储类的所有结构信息,是反射的入口,三种获取方式核心区别:

获取方式 执行时机 类是否初始化 适用场景
类名.class 编译期 不执行类的初始化,不执行静态代码块 工具类、已知类名的场景
对象.getClass() 运行期 类已初始化 已有实例对象,获取对象的实际类型
Class.forName("全类名") 运行期 会加载类,执行静态代码块 动态加载未知类,框架启动加载类、JDBC 加载驱动
个人理解

三种方式的核心差异是类的初始化时机 ,框架大多用Class.forName实现动态加载,业务工具用类名.class,实例对象用getClass()

项目实际使用场景
  1. Excel 导入导出工具 :项目的 Excel 工具类,通过Goods.class获取实体的 Class 对象,反射读取字段和注解,自动映射 Excel 列,不需要每个实体单独写导入逻辑;
  2. Spring IOC 容器启动 :Spring 启动时通过Class.forName扫描并加载配置的类,动态创建 Bean 实例;
  3. 自定义注解解析 :接口请求时,通过handlerMethod.getMethod().getClass()获取方法的 Class 对象,反射读取方法上的权限、日志注解。
面试考点标注

✅ 必问:Class 对象的三种获取方式是什么?各自的区别与适用场景?

✅ 细节:Class.forName()会不会执行类的静态代码块?


2. 反射核心 API 与使用

核心定义

反射是 Java 的动态能力,运行期可以获取类的所有信息,动态创建对象、调用方法、修改属性,是所有框架的底层核心,四大核心 API:

  1. Constructor(构造器)getDeclaredConstructors()获取所有构造器,newInstance()动态创建对象;
  2. Field(字段)getDeclaredFields()获取所有字段,get()/set()读写字段值,setAccessible(true)突破 private 权限限制;
  3. Method(方法)getDeclaredMethods()获取所有方法,invoke()动态调用方法,支持 private 方法;
  4. 注解 APIgetAnnotation()获取类 / 方法 / 字段上的注解。
个人理解

反射的核心是突破编译期的限制,可以拿到类的所有私有信息,实现通用化的逻辑,是框架解耦的核心基础;业务开发中尽量不要手动写反射,但必须理解原理,才能读懂框架源码、排查框架问题。

项目实际使用场景
  1. MyBatis 结果映射:MyBatis 查询数据库后,通过反射调用实体类的 setter 方法,给字段赋值,把 ResultSet 映射为 Java 实体;
  2. Spring 依赖注入@Autowired注解的底层,通过反射给 private 字段赋值,不需要写 setter 方法;
  3. Excel 工具类:项目的批量商品导入工具,反射读取 Excel 行数据,动态给 Goods 实体的字段赋值,实现通用导入。
面试考点标注

✅ 必问:反射的核心 API 有哪些?怎么突破 private 权限的限制?

✅ 场景题:怎么通过反射调用 private 方法、修改 private 字段?


3. 反射的优缺点与性能优化

核心定义
(1)优缺点
  • 优点:极度灵活,运行期动态扩展,实现通用化功能,解耦业务代码和通用逻辑,是所有框架的核心基础;
  • 缺点:① 性能低,比直接调用慢几十到上百倍;② 破坏封装,突破 private 权限,破坏代码安全性;③ 跳过编译期检查,容易出现运行期异常。
(2)性能优化方案
  1. 缓存元数据 :缓存ClassMethodField对象,避免每次反射都重新获取,是最有效的优化手段;
  2. 关闭安全检查setAccessible(true)不仅能突破权限,还能跳过 JVM 的安全检查,性能提升数倍;
  3. 字节码框架替代:用 ASM、CGLIB 等字节码框架,直接生成字节码,比反射性能高很多。
个人理解

反射的性能问题在框架中通过缓存已经被优化到几乎可以忽略,业务开发中不要滥用反射,框架用反射是为了实现通用解耦,是性价比极高的设计。

项目实际使用场景
  1. Excel 工具类优化 :项目的 Excel 导入工具,缓存了所有实体的Field、注解信息,不需要每次导入都重新反射获取,性能提升 10 倍以上;
  2. 注解解析缓存:自定义的权限、日志注解,解析后缓存到本地,不需要每次接口请求都反射解析,提升接口响应速度;
  3. Spring 框架优化:Spring 的 Bean 元数据、方法、字段都做了缓存,不会重复反射,性能损耗极小。
面试考点标注

✅ 必问:反射的优缺点是什么?怎么优化反射的性能?

✅ 必问:为什么 Spring、MyBatis 等框架大量使用反射?

标准答案:为了实现通用解耦,不需要开发者编写重复的硬编码。比如 MyBatis 不需要每个实体写结果映射代码,Spring 不需要每个 Bean 写依赖注入代码,反射是唯一能实现「一次编写,适配所有场景」的方式,是框架的核心灵魂。


模块三:注解核心原理与应用

1. 四大元注解与注解本质

核心定义
  • 注解本质:代码的元数据(标签),本身不影响代码逻辑,只有被解析后才会生效,是代码和逻辑解耦的核心手段。
  • 四大元注解(给注解打标记的注解,自定义注解必用)
    1. @Target:指定注解的作用位置:TYPE(类 / 接口)、METHOD(方法)、FIELD(字段)、PARAMETER(参数)等;
    2. @Retention:指定注解的生命周期,自定义业务注解必须设为RUNTIME
      • SOURCE:编译期保留,编译后丢弃,比如@Override,编译期做语法检查;
      • CLASS:编译到 class 文件,运行期丢弃,默认值;
      • RUNTIME:运行期保留,可被反射读取,所有业务自定义注解必须用这个;
    3. @Documented:注解会生成到 JavaDoc 中;
    4. @Inherited:注解可以被子类继承。
个人理解

注解就是给代码打标记,元注解就是定义标记的规则;生命周期是核心,自定义业务注解必须是RUNTIME,否则反射无法读取,注解就没有意义。

项目实际使用场景
  1. @OperationLog 操作日志注解@Target(METHOD)@Retention(RUNTIME),标记需要记录操作日志的接口;
  2. @RequiresPermission 权限注解@Target(METHOD)@Retention(RUNTIME),标记接口需要的权限标识;
  3. @ExcelColumn 列映射注解@Target(FIELD)@Retention(RUNTIME),标记实体字段对应的 Excel 列名,用于导入导出。
面试考点标注

✅ 必问:四大元注解的作用是什么?@Retention 的三个生命周期的区别?

✅ 必问:@Override的底层原理是什么?

标准答案:@OverrideSOURCE生命周期的注解,编译期检查方法是否正确重写了父类 / 接口的方法,不符合规则编译报错,编译后注解就被丢弃,运行期不存在。


2. 自定义注解的实现与两种解析方式

核心定义
(1)自定义注解实现步骤
  1. @interface定义注解;
  2. 用元注解标记注解的作用位置和生命周期;
  3. 定义注解的属性;
  4. 解析注解,实现业务逻辑。
(2)两种解析方式
解析方式 实现原理 适用场景
反射解析 运行期通过反射获取类 / 方法 / 字段上的注解,执行逻辑 通用工具类、拦截器、简单场景
AOP 切面解析 结合 Spring AOP,拦截标注了注解的方法 / 类,执行切面逻辑 接口日志、权限校验、事务管理等 Spring 项目场景
个人理解

注解 + 反射 / AOP 是企业级项目通用功能的标准实现,把日志、权限、事务等通用逻辑和业务代码完全解耦,不需要每个业务方法重复写通用代码。

项目实际使用场景
  1. @RequiresPermission 权限注解:拦截器 + 反射解析,请求接口时,通过反射获取方法上的注解,校验用户是否有对应权限,无权限则返回 403;
  2. @OperationLog 日志注解:Spring AOP 环绕通知解析,拦截标注了注解的接口,自动记录操作人、请求参数、接口耗时、返回结果,保存到日志表;
  3. @ExcelColumn 注解:Excel 工具类反射解析,获取字段对应的列名,自动实现 Excel 和实体字段的映射。
面试考点标注

✅ 必问:自定义注解的实现步骤是什么?两种解析方式的区别与适用场景?

✅ 必问:@Autowired注解的实现逻辑是什么?

标准答案:Spring 在 Bean 生命周期的「属性填充」阶段,通过反射获取字段 / 方法上的@Autowired注解,根据类型 / 名称从 IOC 容器中找到对应的 Bean,反射给字段赋值,不需要开发者写 setter 方法。


3. Spring 中反射 + 注解的典型应用场景

核心定义

Spring 全家桶的核心设计就是「注解 + 反射 + AOP」,三个最典型的应用:

  1. IOC 依赖注入@Autowired@Value@Bean等注解,反射解析后实现 Bean 的创建和属性注入;
  2. AOP 切面编程@Aspect@Around@Before等注解,反射解析后实现切面逻辑;
  3. 事务管理@Transactional注解,AOP + 反射解析,实现事务的开启、提交、回滚。
项目实际使用场景
  1. 项目订单模块的@Transactional注解,Spring AOP 解析后,实现下单扣库存、扣余额的事务控制;
  2. Controller 层的@RestController@RequestMapping注解,SpringMVC 启动时反射扫描,注册接口路由;
  3. 配置类的@Configuration@Bean注解,Spring 反射解析,把 Bean 注册到 IOC 容器中。
面试考点标注

✅ 高频:Spring 框架中反射 + 注解的典型应用场景有哪些?

✅ 细节:@Transactional注解的底层实现原理。


项目绑定专项巩固(面试加分核心)

1. 你的项目中 MyBatis-Plus 泛型 BaseMapper 的实现逻辑

你的项目中GoodsMapper extends BaseMapper<Goods>,底层完整实现:

  1. BaseMapper<T>是泛型接口,定义了通用的 CRUD 方法(insert、delete、update、select);
  2. MP 启动时,通过反射获取 Mapper 接口的泛型实际类型(比如Goods),拿到实体类的 Class 对象;
  3. 反射读取实体类的@TableName@TableId@TableField等注解,获取表名、主键、字段名;
  4. 通过 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 个典型应用场景

  1. @Autowired依赖注入:Bean 生命周期属性填充阶段,反射获取字段上的注解,从容器中获取 Bean,反射给字段赋值;
  2. @RequestMapping接口注册:SpringMVC 启动时,反射扫描所有 Controller 的方法,获取注解的路由,注册到HandlerMapping
  3. @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 注解的底层原理是什么?

【标准答案】@OverrideSOURCE生命周期的注解,只在编译期生效:

核心作用是编译期语法检查,避免开发者写错重写方法的签名。

【考点对应】模块三:注解生命周期与原理


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();
}

运行期通过动态代理生成实现类,注解的属性值存储在实现类的成员变量中。

注解的两种解析方式

【考点对应】模块三:注解本质与解析原理


实习面试答题加分技巧(绑定你的校园二手平台项目)

  1. 能手写自定义注解 + 反射解析的完整代码(比如自定义 @Log 注解,反射获取方法上的注解并打印日志);

  2. 能口述泛型擦除的完整流程,以及擦除带来的问题与规避方案;

  3. 能说出 Spring、MyBatis 框架中 3 个以上反射 + 注解的应用场景;

  4. 能讲清 PECS 原则,以及通配符上下界的使用场景。

    泛型、反射、注解 面试模拟题(实习面试专属,附标准答案)


    一、基础必考题(8 道,中小厂实习 100% 覆盖)

    1. 什么是泛型擦除?泛型擦除带来了哪些问题?怎么规避?

    【标准答案】

    泛型擦除定义

    泛型是 Java 的编译期语法糖,编译完成后所有泛型信息会被擦除:

  5. 无边界泛型<T>擦除后替换为Object

  6. 有上界的泛型<T extends Number>擦除后替换为上界类型Number

  7. 编译期自动插入强制类型转换,保证运行期类型正确;

  8. 运行期无泛型信息,List<String>List<Integer>的 Class 对象完全相同。

  9. 运行期无法获取泛型的实际类型;

  10. 基本类型不能作为泛型参数,只能用包装类;

  11. 泛型方法签名冲突(擦除后方法签名相同,无法重载);

  12. 无法对泛型类型使用instanceof判断;

  13. 不能创建泛型数组,类型不安全。

  14. 通过反射获取泛型的实际类型(ParameterizedType);

  15. 用包装类替代基本类型,或使用专门的基本类型集合库(Trove、FastUtil);

  16. 避免擦除后方法签名冲突,修改方法名;

  17. ArrayList<T>替代泛型数组,保证类型安全。

  18. 生产者(只读取数据,不写入)用? extends T

  19. 消费者(只写入数据,不读取)用? super T

  20. 既读又写的场景,不要用通配符。

  21. Class:类的元数据对象,获取类的所有结构信息;

  22. Constructor:构造器,newInstance()动态创建对象;

  23. Field:字段,get()/set()读写字段值;

  24. Method:方法,invoke()动态调用方法。

  25. 优点:极度灵活,运行期动态扩展,实现通用化功能,是所有框架的核心基础,解耦业务代码和通用逻辑;

  26. 缺点:① 性能低,比直接调用慢几十到上百倍;② 破坏封装,突破 private 权限,降低代码安全性;③ 跳过编译期检查,容易出现运行期异常。

  27. 缓存元数据 :缓存ClassMethodField对象,避免每次反射都重新获取,是最有效的优化手段;

  28. 关闭安全检查setAccessible(true)跳过 JVM 安全检查,性能提升数倍;

  29. 字节码框架替代:用 ASM、CGLIB 等字节码框架直接生成字节码,性能接近直接调用。

  30. @Target:指定注解的作用位置(类、方法、字段、参数等);

  31. @Retention:指定注解的生命周期;

  32. @Documented:注解会生成到 JavaDoc 中;

  33. @Inherited:注解可以被子类继承。

  34. 编译期,编译器扫描所有标注了@Override的方法;

  35. 检查该方法是否正确重写了父类 / 接口的方法(方法名、参数列表、返回值必须一致);

  36. 如果不符合重写规则,直接编译报错;

  37. 编译完成后,@Override注解会被擦除,运行期不存在。

  38. 框架需要适配所有开发者的业务代码,无法提前知道开发者会写什么类、什么方法;

  39. 反射可以在运行期动态获取类的信息,动态创建对象、调用方法,实现「一次编写,适配所有场景」;

  40. 结合注解,可以让开发者通过简单的注解配置,替代大量重复的硬编码;

  41. 比如 MyBatis 不需要每个实体写结果映射代码,Spring 不需要每个 Bean 写依赖注入代码,都是基于反射实现的。

  42. MP 启动时,扫描所有继承了BaseMapper的 Mapper 接口;

  43. 通过mapperInterface.getGenericInterfaces()获取接口的泛型父接口BaseMapper<Goods>

  44. 强制转换为ParameterizedType,调用getActualTypeArguments()[0]获取泛型的实际类型Goods.class

  45. 反射读取Goods类上的@TableName@TableId等注解,获取表名、主键、字段信息;

  46. 通过 JDK 动态代理生成 Mapper 接口的代理对象,执行方法时自动拼接对应表的 SQL。

  47. 缓存元数据 :第一次导入导出时,反射获取实体的Field、注解信息,缓存到本地 Map,后续直接从缓存取,避免每次都反射获取,性能提升 10 倍以上;

  48. 关闭安全检查 :所有Field调用setAccessible(true),跳过 JVM 安全检查,性能提升数倍;

  49. 批量操作:一次性读取所有 Excel 行,批量反射赋值,不要逐行反射;

  50. 使用高性能 Excel 库:用 Alibaba EasyExcel 替代 POI,EasyExcel 底层做了大量反射优化和内存优化,性能更高;

  51. 避免反射调用方法:直接给字段赋值,不要调用 setter 方法,减少反射调用次数。

  52. Spring 启动时,扫描所有 Bean,通过反射获取类的字段、方法上的@Autowired注解;

  53. Bean 实例化完成后,在populateBean(属性填充)阶段,处理@Autowired注解;

  54. 对于标注了@Autowired的字段:

    • 根据字段的类型,从 Spring IOC 容器中找到对应的 Bean;
    • 调用field.setAccessible(true)突破 private 权限;
    • 通过反射field.set(bean, targetBean)给字段赋值;
  55. 对于标注了@Autowired的 setter 方法:

    • 根据方法参数的类型找到对应的 Bean;
    • 通过反射method.invoke(bean, targetBean)调用 setter 方法赋值;
  56. 如果找不到对应的 Bean,或者找到多个,抛出异常。

  57. 泛型擦除后,T[]变成Object[],可以添加任意类型的对象;

  58. 数组是协变的,String[]Object[]的子类,而泛型不是协变的,List<String>不是List<Object>的子类;

  59. 如果允许创建泛型数组,会出现以下问题:

    java 复制代码
    List<String>[] listArray = new List<String>[10]; // 编译报错
    Object[] objArray = listArray;
    objArray[0] = new ArrayList<Integer>(); // 编译通过
    String s = listArray[0].get(0); // 运行期ClassCastException
  60. 为了避免这种运行期类型安全问题,Java 直接禁止创建泛型数组。

  61. 方法查找开销:每次反射都要遍历类的方法列表,找到对应的 Method 对象;

  62. 安全检查开销:JVM 每次反射调用都要做权限检查、参数检查;

  63. 参数装箱拆箱:反射调用方法时,基本类型参数需要装箱为包装类,返回值需要拆箱;

  64. 无法内联优化:JIT 编译器无法对反射调用做内联优化,而直接调用可以内联,性能大幅提升。

  65. 缓存 Method 对象,避免每次查找方法;

  66. setAccessible(true)关闭安全检查,跳过权限校验;

  67. 提前做参数装箱拆箱,避免运行期自动装箱拆箱。

  68. 编译期解析 :编译器扫描注解,做语法检查或生成额外代码,比如@Override、Lombok 的@Data

  69. 运行期解析 :通过反射获取类 / 方法 / 字段上的注解实例,读取属性值执行业务逻辑,比如@Autowired@Transactional

  70. 所有问题都绑定项目实践

    • 问泛型:主动说「我做校园二手平台的时候,用了 MyBatis-Plus 的泛型 BaseMapper,底层就是通过反射获取泛型的实际类型,自动生成 CRUD SQL,不用每个 Mapper 写重复代码」;
    • 问注解:主动说「我自己实现了 @RequiresPermission 权限注解和 @OperationLog 日志注解,分别用拦截器和 AOP 解析,把通用逻辑和业务代码解耦,非常方便」;
    • 问反射:主动说「我写的 Excel 导入导出工具就是用反射实现的,一开始性能很差,后来缓存了 Field 和注解信息,性能提升了 10 倍多」;
  71. 主动说踩坑经验:比如问泛型擦除,就说「我之前踩过泛型擦除导致的 ClassCastException 的坑,后来才理解泛型是编译期语法糖,运行期没有类型信息」;

  72. 答题逻辑固定为「结论→原理→我的项目实践 / 踩坑」,比单纯背知识点的候选人认可度高很多。

相关推荐
林森lsjs1 小时前
【日耕一题】3. 通过键盘输入一行字符,分别统计出其中英文字母、空格、数字和其它字符的个数。
java·开发语言
小熊猫程序猿1 小时前
Datawhale Task04 具身智能零基础入门 打卡笔记
笔记
问心无愧05131 小时前
ctf show web入门71
android·前端·笔记
夜勤月2 小时前
AQS 与 ThreadPoolExecutor 深度拆解:JDK 高并发底层设计精髓
android·java·开发语言
phltxy2 小时前
Spring AI 应用开发
java·人工智能·spring
码不停蹄的玄黓2 小时前
Arthas 线上问题排查实战:CPU过高、频繁GC
java
Michaelwubo2 小时前
swagger全集通+mock(prism)
java
小陈phd2 小时前
多模态大模型学习笔记(四十七)——跨模态融合策略:早融合、中融合与晚融合核心解析
笔记·学习
进击的小头2 小时前
第7篇:MOS 管最全入门:原理、关键参数、选型、驱动与典型应用
经验分享·科技·嵌入式硬件·学习