《Java 面试八股精讲》系列第 3/14 篇。本系列是我对照开源项目 JavaGuide(https://github.com/Snailclimb/JavaGuide)复习时亲手整理的面试笔记,力求把高频考点压成“能背、能讲、能画”的密度。如有错漏,欢迎评论区指出。
异常
Exception 与 Error
所有异常的共同祖先:java.lang.Throwable,两大子类:
- Exception:程序可处理,可通过 catch 捕获。分 Checked(受检,必须处理)与 Unchecked(非受检,可不处理)
- Error:程序无法处理的错误(如 OutOfMemoryError、NoClassDefFoundError),不建议 catch,JVM 一般会终止线程
Checked vs Unchecked
- Checked:编译期强制检查,未 catch/throws 则编译失败
- Unchecked:不处理也能通过编译
try-catch-finally 如何使用
- try 块:用于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟一个 finally 块。
- catch 块:用于处理 try 捕获到的异常。
- finally 块:无论是否捕获或处理异常,finally 块里的语句都会被执行。当在 try 块或 catch 块中遇到 return 语句时,finally 语句块将在方法返回之前被执行。
- 注意:不要在 finally 语句块中使用 return! 当 try 语句和 finally 语句中都有 return 语句时,try 语句块中的 return 语句会被忽略。这是因为 try 语句中的 return 返回值会先被暂存在一个本地变量中,当执行到 finally 语句中的 return 之后,这个本地变量的值就变为了 finally 语句中的 return 返回值。
实践建议
默认使用 Unchecked Exception,只在必要时用 Checked。
Unchecked Exception(如 NPE)应视为代码 Bug——让它暴露并修复,而非用 try-catch 掩盖。只在"异常是业务逻辑的一部分且调用方必须处理"时用 Checked,例如余额不足异常:强制调用者处理"提示充值"这一正常业务分支。
反射
反射(Reflection):在程序运行时动态获取类信息并操作类或对象(方法、属性)的能力。
优点:
缺点:
典型应用场景:
代理
用代理对象代替对真实对象的访问,在不修改目标对象的前提下扩展其功能。
装饰器 vs 代理
两者结构几乎一样(同接口 + 持引用 + 委托),区别在意图:
- 代理:控制访问、添加横切关注点(日志/权限/缓存),不改变业务语义
- 装饰器:增强/扩展功能,改变业务行为(压缩、加密、重试)
静态代理
编译前手动编写代理类。缺点:不灵活(接口新增方法,目标类与代理类都要改)、每个目标类都要单独写代理类。
实现步骤:① 定义接口及实现类 → ② 代理类实现同一接口 → ③ 注入目标对象,在代理方法中委托调用,前后可插入增强逻辑。
动态代理
允许在不修改源码的情况下对方法做功能增强。主流实现:JDK 动态代理 与 CGLIB 动态代理。
一句话:泛型是"类型的多态",动态代理是"行为的多态"。
静态代理 vs 动态代理
| 代理关系确定时机 | 编译期 | 运行时(动态生成字节码) |
| 代理类数量 | N 个接口 = N 个代理类 | 1 个 Handler 代理所有接口 |
| 接口变更 | 需同步修改代理类 | 无需修改,自动转发 |
| 横切逻辑复用 | 每个代理类重复写 | 一处编写,全局生效 |
| 典型场景 | 简单装饰、少量固定类 | Spring AOP、Dubbo、ORM |
动态代理的代价:
| 性能开销 | method.invoke() 比直接调用慢(JIT 优化后差距缩小) |
| 调试困难 | 运行时生成的 $Proxy0 堆栈不直观 |
| 只能代理接口 | JDK 动态代理要求实现接口(CGLIB 可代理类但受继承限制) |
| 类型安全弱化 | invoke() 参数返回值都是 Object,丢失编译期检查 |
JDK 动态代理 vs CGLIB
框架中的应用
最典型的是 Spring AOP:将事务、日志、权限等与业务无关但被共同调用的逻辑封装为切面,减少重复代码、降低耦合。
注解
Annotation(Java 5 引入)可看作特殊的注释,用于修饰类、方法、变量,供编译或运行时使用。
两种解析方式:
- 编译期扫描:如 @Override,编译器编译时检测方法是否真正重写
- 运行期反射处理:如 Spring 的 @Value、@Component
序列化与反序列化
transient 修饰的变量不会被序列化和恢复。
- 序列化:对象 → 可存储/传输的形式(二进制字节流,或 JSON/XML 文本)
- 反序列化:序列化数据 → 还原为原始对象
典型场景:RPC 网络传输、对象存文件、对象存 Redis、长期保存或跨组件传递。
常见序列化协议
JDK 自带序列化一般不用。常用二进制协议:Hessian、Kryo、Protobuf、ProtoStuff。JSON/XML 属文本协议,可读性好但性能差。
为什么不推荐 JDK 自带序列化
《Java 面试八股精讲》系列目录(加粗为本篇):
3. Java 基础(三):异常、反射、代理与序列化(本篇)





