Kotlin带Lambda参数的方法链式调用格式规范及格式化疑问
搞定IntelliJ对Kotlin链式调用的格式化歧义问题
嘿,这个场景我太熟悉了!当初我写Kotlin链式调用加尾随Lambda的时候,也被IntelliJ的格式化工具坑过好几次,一开始也以为是IDE bug,后来才反应过来是语法歧义在搞鬼。
先把问题说透
你应该是写了类似这样的代码对吧?
someList.fold(mutableMapOf<String, Int>()) { acc, element -> acc[element] = acc.getOrDefault(element, 0) + 1 }.let { countedMap -> println("统计结果:$countedMap") }
你期望的是fold的Lambda块结束后,.let紧紧跟着闭合括号,明确是调用在fold的返回值上。但IntelliJ格式化后却变成了这样:
someList.fold(mutableMapOf<String, Int>()) { acc, element -> acc[element] = acc.getOrDefault(element, 0) + 1 .let { countedMap -> println("统计结果:$countedMap") } }
完全把let塞进了fold的Lambda里面!这就是你猜的那个原因:IDE的格式化器误把let当成了Lambda内部最后一行表达式的链式调用,而不是fold整个函数的返回值调用。
验证&解决办法
你说修改代码后格式化正常,其实核心就是消除这个语法歧义,给IDE明确的提示,这里有几个靠谱的方法:
- 给fold的整个调用套个括号
最直接的方式,用括号把fold的调用包起来,告诉IDE:.let是属于这个括号整体的返回值,不是Lambda内部的东西:(someList.fold(mutableMapOf<String, Int>()) { acc, element -> acc[element] = acc.getOrDefault(element, 0) + 1 }).let { countedMap -> println("统计结果:$countedMap") } - 拆成变量,彻底避免歧义
把fold的结果先存到一个变量里,再调用let,这种方式可读性还更高,完全不会有歧义:val countedMap = someList.fold(mutableMapOf<String, Int>()) { acc, element -> acc[element] = acc.getOrDefault(element, 0) + 1 } countedMap.let { println("统计结果:$it") } - 微调IDE的格式化规则
如果你不想改代码,也可以去IDE设置里调规则:打开Settings→Editor→Code Style→Kotlin→Wrapping and Braces,找到Chained calls的选项,把“Wrap when necessary”改成“Do not wrap”,不过这种方式可能会影响其他代码的格式化风格,不如前两种稳妥。
为啥会有这个歧义?
Kotlin允许把最后一个Lambda参数放在函数括号外面(也就是尾随Lambda),当这个Lambda的最后一行是一个可以链式调用的表达式时,IDE的格式化器就容易犯迷糊——它没法100%确定后续的.是属于Lambda内部的表达式,还是外部函数的返回值。尤其是Lambda内部逻辑比较长的时候,这种误判概率更高。
内容的提问来源于stack exchange,提问作者Zach Thacker
相关产品推荐
相关产品推荐

