已知非空列表场景下的Kotlin惯用法优雅实现方案
Kotlin 已知非空分组取最大值的优雅实现方案
你遇到的问题本质是非空断言!!的语义缺失问题:!!只能告诉编译器"这里我强行转成非空",但没法告诉其他代码维护者"这里为什么一定非空",也没法在异常抛出时给出有效排查信息。下面是几个按推荐度排序的实现方式:
方案1:用groupingBy聚合替代先groupBy再遍历的逻辑
这是最贴合Kotlin标准库设计的写法,从流程上完全规避空值判断,不需要任何强制非空操作:
data class Person(val name: String, val age: Int, val height: Int) val people: List<Person> = // 初始化人员列表 println("Tallest person for each age:") people.groupingBy { it.age } .maxByOrNull { it.height } .forEach { (age, tallestPerson) -> println("$age: ${tallestPerson.name}") }
这里的逻辑是直接通过Grouping做分组聚合,不需要先生成所有分组的临时列表,maxByOrNull对Grouping的返回值是Map<Int, Person>(非空Person类型):因为每个存在的年龄分组必然至少有一个Person元素,聚合时第一次遇到分组元素就会初始化最大值,根本不会出现null值,编译器能直接识别返回值非空,不需要额外断言。
这种写法性能也更好,避免了创建全部分组列表的临时内存开销。
方案2:保留groupBy逻辑时,用明确语义的错误分支替代!!
如果你必须保留groupBy生成的分组列表做其他操作,不要直接写!!,用?: error()把非空的前提直接写在代码里:
people.groupBy{it.age}.forEach{ age, peopleWithThisAge -> val tallestPersonForThisAge = peopleWithThisAge.maxByOrNull { it.height } ?: error("groupBy生成的分组列表不可能为空,此异常理论上不会触发") println("$age: ${tallestPersonForThisAge.name}") }
相比无意义的!!,这种写法有两个明显优势:
- 维护代码的开发者一眼就能看到空分支的设计意图,不需要通读上下文确认这里为什么可以强制非空
- 万一后续代码迭代(比如在取最大值前给分组列表加了过滤逻辑导致列表为空),抛出的异常会携带明确的错误信息,不会像
!!那样只抛出模糊的空指针异常,大幅降低排查成本
方案3:封装通用非空集合取最大值的扩展函数
如果这种"已知非空集合取极值"的场景在你的项目里出现很多,可以封装一个语义明确的扩展函数,统一处理这类逻辑:
/** * 按指定规则取集合最大值,仅用于明确知晓集合非空的场景 * @throws IllegalStateException 集合为空时抛出 */ inline fun <T, R : Comparable<R>> Iterable<T>.maxBy(selector: (T) -> R): T { return maxByOrNull(selector) ?: throw IllegalStateException("无法从空集合中获取最大值") }
使用时直接调用这个扩展函数即可,和老版本Kotlin/Java的非空maxBy API命名保持一致,开发者看到函数签名就知道返回值非空,不需要额外注释:
val tallestPersonForThisAge = peopleWithThisAge.maxBy { it.height }
不推荐直接使用
!!的核心原因是它没有携带任何语义信息,维护者无法区分这是开发者经过严谨逻辑推导后的确定性写法,还是临时凑出来的补丁代码,会增加不必要的心智负担。
内容的提问来源于stack exchange,提问作者J-bob
相关产品推荐
相关产品推荐

