欢迎光临
我们一直在努力

03-Java基础(三)异常-反射-代理-序列化

《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):在程序运行时动态获取类信息并操作类或对象(方法、属性)的能力。

优点:

  • 灵活动态:运行时加载类、创建对象、调用方法。Spring/Hibernate/MyBatis 的 DI、AOP、ORM、注解处理都基于此
  • 解耦通用:可编写通用对象拷贝、序列化、Bean 工具
  • 缺点:

  • 性能开销:动态解析、方法查找、JIT 优化受限(框架通常做缓存优化)
  • 安全性:可绕过访问检查(访问 private)与泛型检查;Java 9+ 模块边界可能抛出 InaccessibleObjectException
  • 可读性差:错误推迟到运行期暴露
  • 典型应用场景:

  • IoC / 依赖注入:Spring 扫描 @Component/@Service 等注解,反射实例化 Bean 并注入依赖(@Autowired)
  • 注解处理:反射是注解的"读取器"——读到 @Value 就去配置文件取值并设置给字段
  • 动态代理与 AOP:JDK 动态代理(Proxy + InvocationHandler)内部通过 Method.invoke 调用真实方法
  • 代理

    用代理对象代替对真实对象的访问,在不修改目标对象的前提下扩展其功能。

    装饰器 vs 代理

    两者结构几乎一样(同接口 + 持引用 + 委托),区别在意图:

    • 代理:控制访问、添加横切关注点(日志/权限/缓存),不改变业务语义
    • 装饰器:增强/扩展功能,改变业务行为(压缩、加密、重试)

    静态代理

    编译前手动编写代理类。缺点:不灵活(接口新增方法,目标类与代理类都要改)、每个目标类都要单独写代理类。

    实现步骤:① 定义接口及实现类 → ② 代理类实现同一接口 → ③ 注入目标对象,在代理方法中委托调用,前后可插入增强逻辑。

    动态代理

    允许在不修改源码的情况下对方法做功能增强。主流实现:JDK 动态代理 与 CGLIB 动态代理。

    一句话:泛型是"类型的多态",动态代理是"行为的多态"。

    静态代理 vs 动态代理

    维度静态代理动态代理
    代理关系确定时机 编译期 运行时(动态生成字节码)
    代理类数量 N 个接口 = N 个代理类 1 个 Handler 代理所有接口
    接口变更 需同步修改代理类 无需修改,自动转发
    横切逻辑复用 每个代理类重复写 一处编写,全局生效
    典型场景 简单装饰、少量固定类 Spring AOP、Dubbo、ORM

    动态代理的代价:

    代价说明
    性能开销 method.invoke() 比直接调用慢(JIT 优化后差距缩小)
    调试困难 运行时生成的 $Proxy0 堆栈不直观
    只能代理接口 JDK 动态代理要求实现接口(CGLIB 可代理类但受继承限制)
    类型安全弱化 invoke() 参数返回值都是 Object,丢失编译期检查

    JDK 动态代理 vs CGLIB

  • JDK 动态代理(官方):要求被代理类实现接口,原理是动态生成接口实现类
  • CGLIB(第三方):不需要接口,原理是动态生成被代理类的子类;因此不能代理 final 类,被代理方法不能是 final/private
  • 效率上大部分情况 JDK 动态代理更优,且随 JDK 版本升级优势更明显
  • 框架中的应用

    最典型的是 Spring AOP:将事务、日志、权限等与业务无关但被共同调用的逻辑封装为切面,减少重复代码、降低耦合。

    注解

    Annotation(Java 5 引入)可看作特殊的注释,用于修饰类、方法、变量,供编译或运行时使用。

    两种解析方式:

    • 编译期扫描:如 @Override,编译器编译时检测方法是否真正重写
    • 运行期反射处理:如 Spring 的 @Value、@Component

    序列化与反序列化

    transient 修饰的变量不会被序列化和恢复。

    • 序列化:对象 → 可存储/传输的形式(二进制字节流,或 JSON/XML 文本)
    • 反序列化:序列化数据 → 还原为原始对象

    典型场景:RPC 网络传输、对象存文件、对象存 Redis、长期保存或跨组件传递。

    常见序列化协议

    JDK 自带序列化一般不用。常用二进制协议:Hessian、Kryo、Protobuf、ProtoStuff。JSON/XML 属文本协议,可读性好但性能差。

    为什么不推荐 JDK 自带序列化

  • 不支持跨语言
  • 性能差:序列化后字节数组体积大,传输成本高
  • 安全问题:反序列化输入可被攻击者控制时,可构造恶意输入执行任意代码

  • 《Java 面试八股精讲》系列目录(加粗为本篇):

  • Java 基础(一):JDK、JRE、JVM、JIT 与 AOT
  • Java 基础(二):equals、hashCode 与 String
    3. Java 基础(三):异常、反射、代理与序列化(本篇)
  • Java 基础(四):IO 模型、泛型擦除与值传递
  • 集合框架:List、Set、Queue、Map 全景
  • JVM(一):内存区域与对象创建
  • JVM(二):Class 文件与类加载
  • JVM(三):垃圾回收算法与收集器
  • Spring(一):基础与 IoC
  • Spring(二):Bean 的声明、注入与生命周期
  • Spring(三):AOP、MVC 与循环依赖
  • Spring(四):事务详解与失效场景
  • Spring(五):Spring Boot 与 Web 注解
  • 数据库基础与系统设计规范
  • 赞(0)
    未经允许不得转载:171主机测评 » 03-Java基础(三)异常-反射-代理-序列化
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址