《Java 编程入门》13.3 异常转换与用户可见错误
从 CommandParser 解析失败和 exportLoans 的 IOException 出发,讲清技术异常、领域异常与用户可见错误的三层分工;再把 NumberFormatException 包装成 InvalidCommandException、在 CLI 边界统一映射 ErrorCode 与退出码,给出真实输出,并列出丢失 cause、重复记日志、吞异常等边界。
tag
从 CommandParser 解析失败和 exportLoans 的 IOException 出发,讲清技术异常、领域异常与用户可见错误的三层分工;再把 NumberFormatException 包装成 InvalidCommandException、在 CLI 边界统一映射 ErrorCode 与退出码,给出真实输出,并列出丢失 cause、重复记日志、吞异常等边界。
从手工 finally 关闭两个资源时异常互相覆盖的问题出发,讲清 AutoCloseable 的契约、try-with-resources 的逆序关闭和异常抑制机制;再把 AuditLog 与 LoanBatch 接到 library 的借阅审计和报表导出上,并给出资源未实现 AutoCloseable、close 抛异常、所有权误用等真实输出与边界。
从 borrow 可能失败的四种情况和 Thread.sleep 的编译错误出发,拆开 Throwable 家族里 Error、受检异常与 RuntimeException 的边界;再建立 LibraryException、ErrorCode 与四个领域异常,接到借阅规则上,并给出漏声明、捕获顺序、finally 吞异常的真实报错。
从 findById 返回 Optional 却总被 get() 的写法出发,讲清 Optional 的创建、map、flatMap、filter 与三种兜底方式,用计数器证明 orElse 会提前求值而 orElseGet 不会;再把 findBook、borrowerOf 和 CLI 查找落到 library,并演示 Stream.iterate 与 anyMatch 的短路。
从循环加临时列表的搜索、排序、统计代码出发,讲清 Stream 管道的源、中间操作与终止操作,说明惰性求值何时真正执行;再用 filter、map、sorted、groupingBy、summarizingInt 改造 library 的查询与统计,并给出流复用、不可变 toList、toMap 重复键的真实报错。
从匿名内部类写搜索条件的冗长代码出发,讲清函数式接口的唯一抽象方法、Lambda 的目标类型与方法引用四种写法,再说明变量捕获与 this 的差异;最后把 Predicate、BookFormatter、Supplier 用到 library 的搜索、格式化与异常延迟创建上,并给出编译错误与真实输出。
用 javap 和真实运行结果拆开类型擦除:List<String> 与 List<Integer> 为什么是同一个类、桥方法从哪来、new T() 和 instanceof List<String> 为什么被禁止;再讲 Class<T>、Supplier<T> 与匿名 TypeRef 三种在运行时恢复类型信息的办法,并落回 library 的泛型仓储。
从 List<Book> 不能传给 List<Object> 的编译错误出发,解释泛型不变性与数组协变的区别,拆开 ? extends、? super 与 ? 的读写能力,再用 PECS 规则改造 library 的 maxBy、copyInto 和查询接口,最后给出通配符捕获与常见误用的真实报错。
从仓储接口重复实现和强制类型转换的真实问题出发,讲清泛型类、泛型方法与有界类型参数如何把类型检查前移到编译期;再把 Repository<T, ID> 与 Result<T> 落到 library 项目,给出可运行的注册图书代码,并列出原始类型、基本类型参数等常见错误的真实报错。
把 library 的图书列表按标题、年份、作者排序:讲清 for-each 背后的 Iterator、遍历中删除的正确写法、Comparable 与 Comparator 的分工、中文 Collator 排序,以及比较器溢出和不一致导致的 TreeSet 丢元素,并给出可复现的真实输出。
用 2 万本书、5 千次查询的真实对比,解释 ArrayList 线性扫描与 HashMap 查找为什么差三个数量级;再讲扩容、负载因子、初始容量、作者索引的维护成本,以及装箱、对象头和集合视图带来的内存开销,给出 library 仓储的容量规划做法。
从 library 仓储的三条查询出发,讲清 List、Set、Map 的契约差异、常见实现类的取舍,以及 equals/hashCode 为什么是放进 Set 和 Map 的门票;再把第 8 章的 ArrayList 仓储改成 Map 版,让 ISBN 查找、借阅状态和会员借阅列表各归其位。
把 library 的 CLI 从字符串命令和 if/else 改成 sealed Command 接口加记录子类型,再用 switch 模式匹配一次性解构、守卫和分发;讲清 permits 的限制、穷尽性检查、模式顺序与 null 分支,并给出非穷尽 switch 与未授权实现的真实编译错误。
把「BORROWED」「RETURNED」这类字符串常量换成 LoanStatus 枚举:用字段和方法承载展示名与开放状态,用 switch 表达式把状态迁移写穷尽,把续借、归还规则放进 Loan record,并用 ordinal、valueOf 与持久化的真实边界说明枚举该怎么用、不该怎么用。
把第 7 章手写的 Book、Member 换成 record:看编译器自动生成访问器、equals、hashCode 与 toString,用紧凑构造器保留校验,处理 record 不能被继承后 StaffMember 的迁移,并用可变组件共享引用、紧凑构造器直接赋字段、继承最终类三个真实边界说明 record 的限制。
从 LibraryService 只持有 LibraryRepository 接口却能调用到内存实现这件事出发,讲清编译时类型与运行时类型、invokevirtual 与 invokeinterface 的查找过程、字段隐藏和构造器调用可覆盖方法的陷阱,并用罚金策略与 javap 输出把动态分派落到可验证的代码上。
把 library 的存储从 LibraryService 里抽出来:用 LibraryRepository 定义契约,用 InMemoryLibraryRepository 提供内存实现;再对比 default 方法、抽象类与模板方法,讲清三者各自解决什么问题、什么时候不该用,并给出接口实现、默认方法冲突与可见性收窄的真实编译错误。
从 library 项目里「正式会员借 3 本、员工借 10 本」的差异出发,讲清 extends、super、构造器调用顺序、覆写规则与里氏替换;再用借阅期限、罚金计算和真实的编译错误,说明什么时候该用继承、什么时候该用组合,避免把复用当成继承的理由。
把 Book 和 Member 收成真正的不可变对象:private final 字段、无 setter、withX 返回新实例、List.copyOf 防御性复制;再把借出状态从 Book 移到 LibraryService 的 onLoan 集合,并用 List.copyOf 与 unmodifiableList 的真实差异说明「复制」和「视图」不是一回事。
让 Book 从「字段袋子」变成「一创建就合法」的对象:讲清构造器的定义、重载与 this(...) 委托,final 字段的明确赋值,静态字段、实例字段、初始化块与构造器的执行顺序;再用 JDK 25 的灵活构造器体,把参数校验放到 this(...) 之前。