kotlin
public fun <T> compareBy(vararg selectors: (T) -> Comparable<*>?): Comparator<T> {
require(selectors.size > 0)
return Comparator { a, b -> compareValuesByImpl(a, b, selectors) }
}
@kotlin.internal.InlineOnly
public inline fun <T> compareBy(crossinline selector: (T) -> Comparable<*>?): Comparator<T> =
Comparator { a, b -> compareValuesBy(a, b, selector) }
这两个函数不冲突吗?方法名是一样的,当只传一个selector时,为什么会直接命中接收vararg selectors的那个?
这是一个很经典的 Kotlin 函数重载 + Lambda/Vararg 调用解析问题,我们来仔细分析。
1️⃣ 为什么不会冲突?
Kotlin 的重载解析规则:
-
非内联函数 和 内联函数 本身不是冲突的,重载只看 函数签名。
-
关键在于调用者传参类型和数量:
vararg selectors: (T) -> Comparable<*>?可以接收 0 个或多个 Lambdaselector: (T) -> Comparable<*>?只能接收 正好 1 个 Lambda
-
当你只传一个 Lambda 时,Kotlin 编译器会优先匹配 最具体的函数,也就是单参数版本:
val comp = compareBy<User> { it.age }
- 单参数 inline 版本正好匹配
(T) -> Comparable<*>? - vararg 版本虽然也能接收一个 Lambda(因为 vararg 可以是一个元素),但它"笼统",不如单参数版本具体
- Kotlin 编译器规则:最具体的匹配优先
所以只传一个 Lambda 时,会命中 inline 的那个。
2️⃣ 如果传多个 Lambda
val comp = compareBy<User>({ it.age }, { it.name })
- 这里就会命中 vararg 版本,因为单参数版本只接受一个 Lambda,而你传了两个
- 这样就可以实现多级排序(先按 age 排,再按 name 排)
@kotlin.internal.InlineOnly 注解功能
@kotlin.internal.InlineOnly 是 Kotlin 标准库里一个内部注解 ,专门用来标记某些函数只能内联使用,不能像普通函数那样在编译后的字节码中被调用。
我们来拆开讲:
1️⃣ 作用
- 标记函数只能内联(inline-only)
- 编译器会在字节码中不生成真实方法 ,调用方必须在编译时展开函数体
- 如果你尝试用反射或通过 Java 调用这个方法,会报错,因为字节码里根本没有方法实现
简单理解:
"这个函数没有实际方法存在,必须内联展开才能使用"
2️⃣ 典型用法
标准库里很多小工具函数都会用这个:
@kotlin.internal.InlineOnly
public inline fun <T> checkNotNull(value: T?, lazyMessage: () -> Any): T {
if (value == null) throw IllegalStateException(lazyMessage().toString())
return value
}
特点:
inline✅@InlineOnly✅- 编译后 不会生成
checkNotNull方法 - 只在 Kotlin 调用点展开
3️⃣ 为什么要用 InlineOnly
- 性能优化:小函数避免创建方法调用和 Lambda 对象
- 限制误用:防止 Java 或 Kotlin 通过反射调用不存在的方法
- 避免冗余字节码:对于只有一行逻辑的小函数,没必要生成真实方法
4️⃣ 与普通 inline 的区别
| 特性 | inline | @InlineOnly |
|---|---|---|
| 编译后生成方法 | 会生成(除非没有被引用) | 绝对不会生成 |
| Java 可见性 | 可见(像普通方法) | 不可见,反射访问会报错 |
| 使用限制 | 可以被调用 | 只能内联使用 |
5️⃣ 实战感受
- Kotlin 标准库里,绝大多数一行工具函数 (
checkNotNull、require、compareBy等)都会加这个注解 - 你在 IDE 里一般不会手动用到它,因为它属于 kotlin.internal,是内部实现细节
💡 总结一句话:
@InlineOnly= "这个函数没有真实方法实现,必须在调用点内联展开,否则无法使用"
inline和crossinline功能
🔹 一、inline 的作用
✅ 1. 本质:函数内联(消除函数调用 + Lambda对象)
加了 inline 后,这个函数在编译时会被直接展开到调用处,而不是生成一个真实的方法调用。
👉 举个例子
调用:
val comparator = compareBy<User> { it.age }
如果没有 inline,大致会变成:
new Comparator<User>() {
public int compare(User a, User b) {
return compareValuesBy(a, b, selector); // selector是一个对象
}
}
👉 会产生:
- 一个
Function对象(selector) - 一次函数调用
🚀 有了 inline 后:
编译器会直接"展开"代码,类似:
Comparator { a, b ->
compareValuesBy(a, b) { it.age }
}
甚至进一步优化(避免对象创建)
👉 好处:
- ❌ 不创建 Lambda 对象
- ❌ 减少函数调用开销
- ✅ 性能更高(尤其在集合操作中)
✅ 2. 为什么 compareBy 要 inline?
因为它是一个高频工具函数(排序用得非常多),而且:
- 参数是 Lambda(selector)
- 很容易产生大量对象
👉 所以内联可以显著优化性能
🔹 二、crossinline 的作用
这个就更关键了 👇
✅ 1. 限制:禁止"非局部返回"(non-local return)
在 Kotlin 中:
👉 普通 inline Lambda 是可以这样写的:
fun test() {
listOf(1, 2, 3).forEach {
if (it == 2) return // ✅ 直接return test()
}
}
👉 这是 Kotlin 的一个特性:
Lambda 里的 return 可以直接返回外层函数
❗ 但在 compareBy 这里不允许!
因为 selector 会在 另一个对象(Comparator)内部执行:
Comparator { a, b -> ... }
👉 也就是说:
- Lambda 并不是"直接调用"
- 而是被"包裹"到另一个对象中延迟执行
🚨 如果允许 non-local return 会发生什么?
compareBy<User> {
if (it.age < 0) return // ❌ 想直接return外层函数
it.age
}
👉 这里会出问题:
selector不是在当前函数栈执行- return 根本不知道该返回到哪里
👉 编译器必须禁止这种行为
✅ 所以用 crossinline
crossinline selector: (T) -> Comparable<*>?
👉 含义:
"这个 Lambda 会被放到别的地方执行,所以禁止你在里面写 return 跳出外层函数"
🔥 三、总结一句话
inline👉 性能优化(展开函数 + 消除Lambda对象)crossinline👉 安全限制(禁止非局部return)
🧠 四、再帮你用一句话记忆
你可以这样记:
inline:👉 "我帮你展开代码,加速执行"crossinline:👉 "但你别乱 return,我控制不了"
⚠️ 五、什么时候必须用 crossinline?
当 Lambda:
✅ 被:
- 存到变量中
- 传给另一个 Lambda
- 放进对象(比如
Runnable/Comparator)
👉 就必须 crossinline
✅ 六、对你这个例子的本质理解
Comparator { a, b -> compareValuesBy(a, b, selector) }
👉 selector 实际上:
- ❌ 不是直接执行
- ✅ 是"被转交"给 Comparator 里的逻辑
所以:
- 需要
inline提升性能 - 需要
crossinline防止非法 return