Scala 01:表达式与类型推导
实验状态:LAB_VERIFIED。代码在 examples/scala-lab/snippets/01/,失败样例在 negative/01/;统一验收记录位于 examples/scala-lab/evidence/20261002-batch01/,Scala CLI 与 sbt 均通过本章断言,两个编译反例命中目标诊断。 金额计算为什么会突然变成 Unit 一个函数先计算金额,再打印日志。如果最后一行从 subtotal + shipping 换成 println(subtotal + shipping),代码并没有停止执行,但返回结果已经改变。Scala 的代码块可以产生值,尾表达式参与确定整个块的结果。日志会输出金额,却不会因此成为那个金额。 这一点把语法、求值与类型联系起来。表达式的静态类型说明它允许怎样使用;运行时值是某次执行的结果;副作用是执行中对外部状态的改变。三个维度不能互相替代。看见日志打印 28.20,不足以证明函数返回 BigDecimal,也不能说明一个分支不会抛异常。 本章在 Scala 3.3.7 下,从订单金额的小例子推导代码块、分支和底类...
Scala 00:工具链与可复现实验
本篇建立 Scala 系列的冻结实验基线。主线统一入口现已覆盖 00–39,Scala CLI 与 sbt 均实际运行 40 章断言,52 个隔离编译反例命中目标诊断。首批 examples/scala-lab/evidence/20261002-batch01/ 保留七章历史记录;全系列记录与各独立模块实验见工程 README 和每篇 RUN 附件。 从订单金额开始 两件商品各售 12.50 元,另一件售 3.20 元,总额应该是 28.20 元。对这样一个程序,“编辑器没有红线”“命令打印了数字”“测试通过”分别说明不同的事情。编辑器通常有自己的索引与编译服务;运行命令可能使用默认版本;打印出的数字可能根本没有经过业务断言。第一篇需要建立一个能排除这些差异的最小闭环。 实验采用 Scala 3.3.7、JDK 21、Scala CLI 1.9.1 与 sbt 1.11.7。这是系列冻结组合,方便后续重现求值、初始化和型变问题,不是最新版本推荐。Scala 3.3.7 的定位与 JDK 支持范围分别由发布公告和JDK 兼容说明支持。语言版本、构建工具版本、实际启动的 Jav...
Java常用类库-E08-MapStruct与Lombok的生成代码契约
删除手写代码之前先保留行为契约 商品输入对象需要转换成展示对象,还需要支持更新已有对象。MapStruct 可以生成字段映射,Lombok 可以生成访问器、builder 和相等方法。代码行数减少之后,null、集合引用和 hashCode 行为仍然存在;如果不检查生成结果,就可能把原本看得见的决策变成构建过程中隐含的默认值。 本篇使用独立 Maven 模块,实际运行 MapStruct 1.6.3 与 Lombok 1.18.42,并显式加入 lombok-mapstruct-binding 0.2.0。固定源码分别为 b4e25e49deae707b50ce061172e114292b414a23 与 2031eb0880942b5f0b7281580f6e877a3e87279a。所有应用源码保持 Java 8,在 JDK 8 和 JDK 21 分别 clean verify,没有用已经生成的 class 代替处理器执行。 完整项目见 下载处理器实验工程,测试见 ProcessorContractTest.java,生成映射见 ProductMapperImpl.java,...
Java常用类库-E07-Resilience4j的组合顺序与调用边界
一次查询为什么可能变成三次调用 商品查询依赖一个偶尔失败的服务,外层增加重试后,调用方可能看到成功,服务端却执行了三次。再加熔断器,如果它观察每次尝试,第一次失败就可能阻止后续尝试;如果它只观察整个重试结果,最终成功又可能让它记录零失败。同样的组件和参数,装饰顺序已经改变了实际负载与统计含义。 Resilience4j 将 Retry、CircuitBreaker、RateLimiter、Bulkhead、TimeLimiter 分成独立模块,并用函数装饰器组合。组合能力本身不提供唯一正确的执行顺序。评审应先写出调用嵌套和每层观察对象,再决定配置,而不是列出启用了哪些开关。官方组合讨论 本章固定 Resilience4j 2.3.0,源码提交 c2c6575114fc0650177fb21e1ff967f14acde39c。它进入系列的 modern 工程,编译目标 Java 17,实测 JDK 21;不放入 Java 8 实验矩阵。实验是本机可控失败函数与真实线程任务,没有连接外部服务,也不把计数器实验声称为真实网络取消验证。固定版本工程 为每层定义观察单位 逻辑调用是业务发起...
Java常用类库-E06-Vavr的结果类型与结构共享
导入失败是否应该成为一个值 商品数量解析可能成功,也可能遇到非法文本。普通方法可以抛异常,也可以返回包含错误信息的结果。Vavr 的 Try 把许多异常转换为 Failure,Option 表达有值或无值,不可变集合则让更新生成新结构。它们分别改变错误传播、值表示与更新方式,不能只用“函数式写法”概括全部行为。 本章固定 Vavr 0.10.7,而非追随默认分支。官方 v0.10.7 对应源码提交 3af14df99c6a99ecd984e516d5f5294335cfbe84,POM 的 Java 目标为 1.8;实验在 Zulu 8u472 和 Corretto 21.0.11 执行。这里选择的是与本系列 Java 8 基线相容的版本,不声称它是当前最新版本。固定 POM 需要研究的重点是转换损失:Some(null) 转成 JDK Optional 后会怎样,Failure 转成 Option 是否还保留原因,旧集合与新集合是否共享元素。编译器能检查类型,却不会替接口设计者决定哪些状态可以被合并。 Option.map 不自动消除 null JDK Optional.of(...
Java常用类库-E05-命令行配置覆盖与反射绑定白名单
导入配置需要一条明确的数据路径 商品导入程序同时接受默认值、配置文件和命令行选项,最终得到每批处理条数。外部输入仍是文本,但运行逻辑需要一个有限范围内的正整数。Commons CLI 可以识别选项,Configuration 可以组合来源,BeanUtils 可以把文本赋给属性;这三步都不能单独证明最终配置符合业务要求。 本篇把流程限定为解析参数、按优先级取值、受控插值、白名单绑定和领域校验。测试全部使用合成参数数组与内存配置,没有执行 shell 命令,也没有读取真实环境变量或系统配置。完整源码见 Extension05Test.java,复跑说明见 RUN.md。 固定版本为 Commons CLI 1.11.0、Configuration 2.15.1、BeanUtils 1.11.0,均选择正式的 Java 8 兼容发行版。对应源码提交分别是 d74613321325f6081fa0206c4ab608ab65d80e2e、51f8140ab63e2904c6a5c46fecf841fd5e34c492 与 9c0b5cf007818c3fb1ac36d5ed394d56...
Java常用类库-E04-Gson与Jackson的同一JSON契约
JSON 语法相同,不代表对象协议相同 商品接口收到 quantity 和 name,需要保留未提供与显式 null 的差别,拒绝不允许的结构,并把时间写成规定格式。将 Gson 换成 Jackson,或反向替换,不能只检查“同一个样例可以解析”:未知字段、默认值、字段发现、数字转换和重复键都可能改变结果。 本篇固定 Gson 2.14.0,源码 3ff35d6269894901ab8006258395aafc4b9765cd;Jackson Databind 与 JavaTime 模块固定 2.20.1,Databind 源码 4164a2b47d3795a28f7cb838a02c27b77f9adaf4。实验在 Java 8 与 Java 21 使用同一输入集。完整代码见 Extension04Test.java,命令见 RUN.md。 比较分成默认行为与显式对齐两步。默认差异帮助识别迁移风险;对齐后的断言才验证业务协议。没有针对这些样本运行 JSON 性能基准,因此本文不从 API 风格或默认功能多少推断哪个库更快。 缺失字段与显式 null 先从类型模型区分 测试 Pr...
Java常用类库-E03-原始类型集合的缺省值与装箱成本
商品编号索引不只是在替换 Map 类名 商品索引用整数编号查找结果,现有实现是 HashMap<Integer, Integer>。一次查询看起来只调用 get,但调用点需要把 int 表示为 Integer,容器也要组织对象键。fastutil 与 Eclipse Collections 提供原始类型键接口,可以减少这层表示成本;与此同时,缺失值、null 和迭代方式也会变化。 本篇固定 fastutil 8.5.16,源码 aa30fa7d93bf06fec45c4f6f572a1f6a38047d9f,以及 Eclipse Collections 11.1.0,源码 fb6abf7fe655ff04acf616158bce49725f2df5ad。后者选择 Java 8 兼容版本,不据此推断当前所有版本都支持 Java 8。完整测试见 Extension03Test.java,运行说明见 RUN.md。 实验分成两种类型组合。int→int 用于说明零与缺失的协议;int→Object 用于和现有 HashMap<Integer, Integer> ...
Java常用类库-E02-原始数组数值边界与IO所有权
导入商品数量时有三种不同边界 商品导入流程从文件读入数量,经过数值转换,再交给使用 List 的校验接口。流程只有几步,却包含三类独立问题:数组与列表是否共享修改、超出数值范围时怎么办、读取结束后由谁关闭资源。Guava 的 Primitives、Math 与 IO 分别提供相关能力,使用时应围绕契约组织,而不是把它们当作一组缩写方法。 本章固定 Guava 33.5.0-jre,所有代码采用 Java 8 基线,分别在 Java 8 和 Java 21 执行。实验输入很小,目的在于验证边界,不测吞吐。文件来源用可控的 InputStream 与 Reader 实现,明确制造短读与读取异常,避免只有理想内存流的成功路径。 Ints.asList 是数组视图 输入数组为 [1,2,3],调用 Ints.asList 后得到 List。把原数组第一个元素改成 10,列表立即读到 10;再调用列表 set,把第二个元素改成 20,原数组也变成 [10,20,3]。这是双向共享的视图,不是装箱后独立保存的新列表。 视图长度固定,add 被拒绝。它可以 set,并不意味着能够扩容;它不能 ...
Java常用类库-E01-Graph的边模型与邻接结构
同一对商品节点为什么需要两条边 商品发布前需要检查依赖:SKU 依赖价格,也依赖库存。若只关心“有没有依赖”,一个有向边足够。如果还要记录检查成本,可以给这条关系附加数值。如果两条规则分别来自两个供应商,并且可以独立启停和审计,那么同一对节点之间就需要两个独立的边对象。 这三种需求分别接近 Guava 的 Graph、ValueGraph 与 Network。选择接口的关键是边有没有独立身份,而不是哪种接口方法更多。本章固定 Guava 33.5.0-jre,使用同一组商品依赖输入,在 Java 8 与 Java 21 验证边、视图、顺序与快照。官方图模型说明也把独立边对象作为选择 Network 的重要依据。GraphsExplained 实验节点是 sku、price、stock、orphan。最后一个没有依赖关系,专门用来检查“没有边的节点”会不会在模型转换时丢失。边输入则包括 sku 到 price 的两个来源,以及 sku 到 stock 的一个来源。这个小数据集同时包含方向、重复端点和孤立节点,足以区分三种表示。 Graph 保存关系,ValueGraph 保存关系的...

