Java常用类库-29-CSV方言与流式记录契约
一行文本不一定是一条商品记录 商品导入文件有 sku、label、note 三列。label 可以包含逗号,note 可以包含换行,某个备注也可能为空。用 readLine 加 split 处理,既会把引号内的逗号拆成多列,也会把一个多行备注拆成多条记录。错误通常不表现为立即异常,而是字段错位后继续进入后续业务。 本篇固定 Commons CSV 1.14.1,提交 e14ef86d34211f9d1b9b6040c5f6dbafc93ba907。BOM 处理使用 Commons IO 2.22.0。实验契约是按明确方言读取逻辑记录,拒绝缺失或重复表头,逐条校验列数,再打印为同一方言;往返验收比较字段值,不要求字节逐一相同。完整测试见 Chapter29Test.java,命令见 RUN.md。 CSV 是结构语法,商品字段约束是业务规则,电子表格公式则属于消费程序的解释规则。三个层面必须分开。一个 CSVParser 成功返回的记录,仍然可能列数不符、价格格式错误,或者在电子表格中被解释为公式。 方言必须成为接口的一部分 测试以 CSVFormat.RFC4180 为起点,启用...
Java常用类库-28-Base64严格度与摘要认证边界
导入令牌需要先确定字节协议 商品导出系统把二进制校验值放进 URL,把文件摘要放进日志,并给回调请求计算认证码。这些字段看起来都是一段字符,但要求不同:Base64 解决字节如何表示,摘要用于得到固定长度指纹,HMAC 则依赖密钥认证消息。把它们都叫作“加密字符串”,会使协议中的保密性、完整性与来源认证混在一起。 本篇固定 Commons Codec 1.22.1,提交 dc8f6c832a30f524bf12a5ae7ab013e4ad7c088f,对照 JDK 8 的 Base64、MessageDigest 与 Mac。完整测试见 Chapter28Test.java,命令见 RUN.md。测试只使用公开向量和合成字节,没有生产密钥。 首先确定协议:输入字符串如何变成字节,使用哪种字母表,是否允许填充和空白,多个不同文本能否代表同一字节串,校验值覆盖哪些字段。工具类可以完成转换,却不能替接口协议作出这些选择。 URL 字母表与填充是两个参数 实验使用两个字节 FB FF。标准 Base64 编码为 +/8=,Commons Codec 的 URL-safe 便捷方法编码为 ...
Java常用类库-27-转义上下文与受控文本插值
同一段商品文本会进入不同语法 商品名称既可能显示在页面正文,也可能放进 HTML 属性、JSON 接口和 XML 导出。另一个需求是将模板中的商品编号替换为实际值,或者找出拼写相近的编号。这些任务都接收字符串,却不共享一种“安全处理”方法:输出编码取决于目的语法,插值取决于允许访问的数据,相似度计算取决于输入规模。 本篇固定 Commons Text 1.15.0,提交 04e937470d3679cc163df85d82d5b6d2e3e71128。实验在 JDK 8 与 JDK 21 上使用同一组输入,只访问内存中的合成商品数据,不读取真实环境变量或文件。完整代码见 Chapter27Test.java,执行方式见 RUN.md。 业务契约分成三个可验证条件:文本按照最终输出位置编码;模板只能查询明确提供的键,未知键报错,值中的占位符不递归展开;距离计算先限制输入长度,再判断是否超过距离阈值。缺少其中任一条件,都可能出现输出语法被改变、模板获得额外能力或单次计算成本失控的问题。 HTML 正文的转义结果不能直接用于所有属性 实验把 <b>&"' ...
Java常用类库-26-路径规范化与真实目录边界
文件名处理必须区分文本与文件系统 商品导入允许提交报表文件名,应用准备把文件写入一个专用目录。对 reports/../catalog.csv 做字符串规范化,可以得到 catalog.csv;这个结果表达了词法路径,却没有说明文件系统中的 reports 是否为符号链接,也没有说明 catalog.csv 是否已存在。字符串干净与写入位置受控,是两个不同的验收条件。 本篇使用 Commons IO 2.22.0,固定提交 c14acc16f73e44a75b2062b17aacb26c4feda746,并用 Java 8 NIO 完成实际目录检查。实验策略刻意收窄:只接受私有目录下的新相对文件名,不覆盖已有对象,拒绝父路径与反斜杠,不允许不可信并发写者。这个边界用于后续受控解包示例,不宣称是任意共享目录的通用安全写入器。 完整测试见 Chapter26Test.java,辅助实现见 SafePaths.java,命令见 RUN.md。测试使用独立临时目录,实际创建符号链接与已有文件;没有把只打印一个规范化字符串当成落盘验证。 normalize 会消除语法,也会消除意图线索 F...
Java常用类库-25-流所有权与有界读写
导入文件不能只检查读到了字符串 商品导入接收一个输入流,期望得到 UTF-8 文本。成功读取一个小文件,并不能证明导入器已经正确处理资源与输入边界:网络流可能每次只返回一个字节,文件可能带字节顺序标记,读取可能在中途失败,用户也可能提交超过内存预算的内容。方法名称中的 copy、toString、readFile,并没有统一规定谁负责关闭、允许读多少或失败后保留什么。 实验固定 Commons IO 2.22.0,源码提交为 c14acc16f73e44a75b2062b17aacb26c4feda746。Java 8 与 Java 21 分别运行完整的 Chapter25Test.java,有界复制实现见 BoundedIo.java。项目和命令见 复跑说明。本篇只使用受控字节数组与临时文件,不把本机文件测试当成网络超时验证。 短读是一次进展,不是文件结束 InputStream.read(buffer) 返回正数,只表示这次读到了这些字节;返回值小于缓冲区长度并不表示 EOF。结束信号是 -1。测试中的 ShortInput 每次最多提供一个字节,内容为 UTF-8 的“商品...
Java常用类库-24-集合装饰器的校验转换与同步入口
包装集合后,原引用仍然存在 目录模块用 PredicatedCollection 限制标签不能为空。通过包装的 add 插入空字符串会抛异常,但另一个调用方持有原 ArrayList,直接 add 空字符串后,包装中仍然能读到这个非法值。包装没有复制底层集合,也无法拦截绕过它的调用。 同步包装和转换包装面临相同的入口问题。同步方法只在通过包装调用时持锁;转换只在指定入口发生。把原始集合继续交给其他模块,就同时保留了未校验、未同步或未转换的写入路径。类型增加一层装饰,不等于对象图中所有路径都受到相同约束。 本篇固定 Commons Collections 4.6.0、Guava 33.5.0-jre,以 Java 8 API 编译,在 Zulu 8.0.472 与 Corretto 21.0.11 运行。完整测试用返回值、转换次数和 Thread.holdsLock 观测包装行为;运行说明给出复跑命令。前置为 第 02 篇的引用关系。 PredicatedCollection 检查经过它的写入 实验创建空 ArrayList,再套上谓词“非 null 且非空串”。包装 add(“”...
Java常用类库-23-Collections4多值结构的迁移差异
相同数据结构名称不保证相同删除行为 商品导入含三次 A 和一次 B。计数结构需要保留 A 的次数,普通 Set 不够用。Guava Multiset 与 Commons Collections Bag 都能表示重复次数,但对 remove(“A”) 的解释不同:Guava 删除一次,Commons Bag 删除全部 A。若把一种实现换成另一种,只核对添加与读取结果,会漏掉这个改变库存数量的差异。 双向映射也有类似问题。已有 A→x,再插入 B→x,Guava HashBiMap.put 拒绝重复值;Commons DualHashBidiMap.put 则删除旧键 A,让 x 对应 B。两者最终都维持值唯一,却采用不同冲突策略。数据结构的不变量相同,不代表每一步操作接受相同输入。 本篇固定 Commons Collections 4.6.0、Guava 33.5.0-jre,Java 8 API,在 Zulu 8.0.472 与 Corretto 21.0.11 运行。完整测试和 运行说明使用相同样本核验计数、多值与反向索引;前置为 第 10 篇。 Bag 的 size 与 it...
Java常用类库-22-Pair可变包装与反射Builder的边界
两个字符串还没有表达商品身份 商品目录用商户编号和商品编号共同定位一件商品。Pair<String, String> 可以容纳这两个值,但 left、right 没有表达哪个是商户,也没有声明 null 是否允许。类型相同还意味着调换顺序仍能编译,错误只能在调用约定或运行结果中发现。 Pair 适合局部算法里寿命较短的两值返回;跨模块业务身份通常更适合具名类型。具名类型能够在构造处拒绝缺失编号,规定相等关系,并把读取方法命名为 merchantId、productId。它未必需要框架,Java 8 的一个 final 类即可表达这些约束。 本篇使用 Commons Lang 3.20.0,示例以 Java 8 编译,在 Zulu 8.0.472 和 Corretto 21.0.11 运行。完整实验包含 Pair、Triple、MutableInt、反射 builders 与手写 ProductKey;运行说明沿用系列工程。第 09 篇讨论的浅层不可变,在这些便利类型中同样存在。 Pair 同时具有 Map.Entry 语义 Pair 的固定源码实现 Map.Entry...
Java常用类库-21-数值布尔与枚举的失败语义
坏库存不能自动变成零 库存导入字段 quantity 收到文本 bad。Integer.parseInt 抛异常,NumberUtils.toInt 返回默认的零。系统因而可以继续执行,但“库存为零”和“库存无法解析”变成了同一个结果。若后续按零库存下架商品,一次格式错误就会改变商品状态。 默认值不是独立于业务的容错功能。它把原本属于错误通道的信息折叠进正常值域。商品数量、金额、开关和运行模式都可能受影响,只是折叠的值分别表现为零、false 或某个枚举常量。选择工具函数之前,应先决定哪些失败允许退回默认配置,哪些必须阻止当前记录生效。 本篇固定 Commons Lang 3.20.0,Java 8 API,运行环境为 Zulu 8.0.472 与 Corretto 21.0.11。完整实验包含非法文本、溢出、指数、布尔三态、未知枚举和 Validate。运行说明沿用系列工程;字符串缺失与空白定义可参照 第 20 篇。 toInt 将解析失败映射到默认值 本实验把 null、空串、bad、带前导空格的 " 2"、2147483648、1e3 分别传给 Numb...
Java常用类库-20-StringUtils的空值与边界契约
缺失名称不一定等于空名称 商品更新接口收到缺失的 name,可能表示保留原名称;收到空字符串则可能表示主动清空。若入口统一调用 StringUtils.defaultString,两者都变成空字符串。下游没有再抛 NullPointerException,但原先可以区分的两个业务动作已经合并。 StringUtils 所谓 null 安全,通常指方法对 null 定义了返回行为,不意味着所有方法返回相同默认值,更不意味着结果符合业务约束。trim(null) 返回 null,split(null) 返回 null 数组,defaultString(null) 返回空字符串。读取这些返回值的下一步仍可能失败,例如直接访问 split 返回数组的 length。 本篇固定 Commons Lang 3.20.0、Guava 33.5.0-jre,代码以 Java 8 为基线,运行环境为 Zulu 8.0.472 与 Corretto 21.0.11。完整 测试源与 运行说明对照空白、切分、截取及默认值;前置语义见 第 01 篇。 trim 与 strip 使用不同判定 trim 沿用...

