用途
本技能用于把 Kotlin(以及 Java)代码从 kotlinx.collections.immutable 0.3.x / 0.4.x 迁移到最新的 0.5.x。0.5.x 系列将持久化集合上所有返回副本的方法重命名为分词形式(依据 KEEP-0459),并以 WARNING 级别弃用旧名称,同时提供 ReplaceWith 提示。迁移是机械的、二进制兼容、保持语义的调用点重命名——参数、顺序和返回类型都不变,只有名称改变。
迁移由编译器驱动:提升版本、重新编译、逐个修复弃用警告,警告本身会指出替代名称。权威依据是 0.5.0-MIGRATION.md。
适用条件
先检查项目当前使用的版本:
- 0.3.x 或 0.4.x(任何 0.5.0 之前的版本)→ 执行下述迁移。
- 已在 0.5.x 但不是最新 → 将版本设为最新的 0.5.x 后停止。所有 0.5.x 版本共享相同的重命名,因此同一系列内的小版本提升不会新增弃用,也无需重新编译。
- 已在最新 0.5.x,或 0.6.x 及以后 → 无需操作。
迁移步骤
1. 找到构建命令
查看 README.md、CLAUDE.md 或 AGENTS.md 了解项目如何构建;如果没有写明,则从构建文件推断——Gradle(./gradlew)、Maven(mvn 或 ./mvnw 包装器)、Bazel(bazel 包装器)或自定义脚本。记录编译命令(以及测试命令)。在多模块项目中,只需处理使用该库的模块以及你改动的模块,不必全仓库构建。
2. 基线编译
在当前版本上编译并确认通过。如果现在就无法构建,就无法区分迁移后的错误与既有错误——先获得可用的编译命令。
3. 提升到最新 0.5.x
找到版本被固定的位置——用 grep -rn kotlinx-collections-immutable 搜索构建文件即可找到(版本目录、构建脚本、gradle.properties、pom.xml 等)——将其设为 Maven Central 上最新的 0.5.x(-beta 也可以)。如果构建固定了构件哈希(如 gradle/verification-metadata.xml),也要更新它们——最省事的做法是直接从依赖校验失败信息中复制新构件的校验和,只添加那一条记录,而不是重新生成整个元数据文件。此提升是二进制兼容的;旧代码仍可编译,只是产生警告。(如果依赖因 Kotlin 元数据版本错误而无法解析,说明项目的 Kotlin 太旧,无法使用 0.5.x 构件——先升级 Kotlin。)
4. 重新编译并修复警告
重新编译。每个被重命名的方法都带有 @Deprecated(WARNING, ReplaceWith(...)),因此编译器会为每个调用点发出一个警告,指出替代名称(例如“Use removingAll() instead”)。应用该重命名。重复“编译 → 修复”,直到不再有 kotlinx.collections.immutable 弃用警告。(对于多平台项目,编译一个目标即可暴露共享的调用点。既有的工厂弃用如 immutableListOf → persistentListOf 也以同样方式出现——一并应用。)提升版本后立即出现的编译失败,正是由这些弃用(以及固定哈希时一次性的依赖校验错误)导致的——继续应用警告指出的重命名即可;不要为了清除它而重新运行依赖解析或元数据重新生成命令。
信任编译器——绝不要按名称查找/替换。相同的名称也存在于 MutableList / MutableMap / MutableSet 以及 .Builder 类型上,这些是原地修改的,并未被弃用。只有编译器标记的调用点(接收者静态类型为 Persistent*)才重命名;如果编译器没有标记,就不要动。
重命名后出现 Unresolved reference,意味着该分词名称不在那个接收者上——你把一次重命名拆开了。一次重命名只有在声明和所有调用点一起移动时才能编译。库已经为 kotlinx 类型做好了这一点,所以重命名它们的调用点可以直接工作——但对于其他仅仅共享这些名称的类型并不成立。当重命名的调用无法解析时,有两种情况:
- 接收者与本库无关(一个 Mutable*、一个 .Builder、某个其他类型上的同名方法)——重命名是错的;还原该调用点。
- 接收者是代码库自身正在迁移的项目类型——它实现了 Persistent*,或者是项目自己的包装类,其方法沿用这些名称并要相应重命名。重命名是对的但只做了一半:同时重命名声明及其其他调用者,使调用可解析。(实现者上的弃用 override 见第 5 步。)
依据接收者的声明类型判断,绝不要依据方法名——一个名为 Persistent* 的字段可能持有其他类型。当你无法依赖快速重新编译、只能靠阅读源码重命名时,这一点尤其重要。
Java 调用者。只有当构建报告 javac 弃用警告(-Xlint:deprecation,通常关闭)时,重新编译才会标记它们。如果没有,就在导入该库的 .java 文件中搜索旧名称,并重命名接收者为 Persistent* 类型的调用。
重命名之后,编译器可能报告某些 @Suppress("DEPRECATION") 无效——删除它们(先重新阅读该区域,以防它仍覆盖其他内容)。
5. 自定义实现者
如果项目有实现 PersistentList / PersistentMap / PersistentSet / PersistentCollection 的类,它们的弃用 override 也需要迁移。查找它们:
grep -rnE --include='.kt'
'(class|object|interface)\s+\w[^:]:\s*[^{]\b(PersistentList|PersistentMap|PersistentSet|PersistentCollection)\s<' .
在 Windows PowerShell 上,Select-String 相当于 grep:
Get-ChildItem -Recurse -Filter .kt | Select-String '(class|object|interface)\s+\w[^:]:\s*[^{]\b(PersistentList|PersistentMap|PersistentSet|PersistentCollection)\s<'
(确认匹配项确实把该接口列为超类型,而不仅仅是字段类型或类型参数。)对每个实现,把实现移入新的分词方法,并让弃用 override 委托给它:
override fun adding(element: E): MyList<E> = /* real implementation */
@Suppress("OVERRIDE_DEPRECATION") override fun add(element: E): MyList<E> = adding(element)
如果分词方法之间相互调用,让这些调用走分词兄弟方法,而不是弃用名称。(仅当 override 方法体本身仍调用弃用成员时,才在 suppress 中加入 "DEPRECATION"。)现在就做很重要:到 0.6.0 时旧名称会变成编译错误,到 0.7.0 时会被移除。实现者指南见 0.5.0-MIGRATION.md。
6. 运行任何有文档记录的后续步骤
在重命名编译干净之后再做这一步,这样即使时间不够,调用点的工作也已完成。有些项目记录了依赖变更后需要运行、编译器不会暴露的步骤——最常见的是重新生成依赖校验元数据(第 3 步中的 gradle/verification-metadata.xml 哈希)。通常第 3 步的单条记录修复就够了;只有当那一条不够时,才回退到项目文档记录的完整重新生成流程(README.md / CONTRIBUTING.md / CLAUDE.md / AGENTS.md)。只运行一次——完整的 --write-verification-metadata / “解析所有依赖”过程会重新解析整个依赖图,很慢,且重复运行很少改变结果。然后再次确认构建干净。
重命名对照
- PersistentCollection — add→adding,addAll→addingAll,remove→removing,removeAll→removingAll,retainAll→retainingAll,clear→cleared
- PersistentList(含上述,另加)— add(i, e)→addingAt,addAll(i, c)→addingAllAt,set(i, e)→replacingAt,removeAt→removingAt
- PersistentMap — put→putting,putAll→puttingAll,remove(k)→removing,remove(k, v)→removing,clear→cleared
构建器(PersistentList.Builder 等)不重命名——它们原地修改,因此其命令式名称保留。
链接
- 0.5.0-MIGRATION.md — 上游指南(权威依据,含实现者细节)
- KEEP-0459 — 命名理由