You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin多类型参数扩展函数能否仅指定单个显式类型参数?

引言

我在Kotlin中定义了一个泛型转换扩展函数,用于简化将类型为C的当前对象转换为另一个类型为T的对象(即声明的receiver)的流程,附带的转换action会将receiver作为当前上下文,同时支持访问原始对象:

inline fun <C, T, R> C.convertTo(receiver: T, action: T.(C) -> R) = receiver.apply {
    action(this@convertTo)
}

调用方式如下:

val source: Source = Source()
val result = source.convertTo(Result()) {
    resultValue = it.sourceValue
    // and so on...
}

我发现我经常在通过无参构造器创建receiver的场景下使用该函数,因此想进一步简化使用流程,新增一个版本的convertTo(),支持基于类型自动构造receiver,实现如下:

inline fun <reified T, C, R> C.convertTo(action: T.(C) -> R) = with(T::class.constructors.first().call()) {
    convertTo(this, action) // 调用第一个版本的convertTo()
}

但我无法按照如下方式调用:

source.convertTo<Result>() {}

因为Kotlin要求传入全部三个类型参数。

问题

基于上述背景,Kotlin中能否定义带有多个类型参数的泛型函数,仅显式传入一个类型参数,其余类型由调用上下文自动推断?

补充示例(@broot 提供)

假设标准库中没有filterIsInstance(),我们需要自行实现该函数(或我们是标准库的开发人员)。假设我们可以使用@Exact注解,这对本例很重要。最优的定义方式应该是:

inline fun <T, reified V : T> Iterable<@Exact T>.filterTyped(): List<V>

最便捷的调用方式如下:

val dogs = animals.filterTyped<Dog>() // 编译报错

但我们只能选用以下其中一种变通方案:

val dogs = animals.filterTyped<Animal, Dog>()
val dogs: List<Dog> = animals.filterTyped()

最后一种实现方式体验还不算太差。

如果我们需要实现一个筛选指定类型元素并执行映射的函数:

inline fun <T, reified V : T, R> Iterable<T>.filterTypedAndMap(transform: (V) -> R): List<R>

我们同样希望能按如下方式调用:

animals.filterTypedAndMap<Dog> { it.barkingVolume } // 编译报错

但实际只能这么写:

animals.filterTypedAndMap<Animal, Dog, Int> { it.barkingVolume }
animals.filterTypedAndMap { dog: Dog -> dog.barkingVolume }

这个例子为了便于理解故意设计得比较简单,所以体验还不算太差。但实际场景中函数可能更复杂,有更多类型参数,Lambda会接收更多参数等,此时使用难度就会大幅提升。用户收到类型推断错误后,需要仔细阅读函数定义才能明白缺少什么、需要在哪里显式指定类型。

额外说明:Kotlin不允许编写cat is Dog这样的代码,但允许cats.filterIsInstance<Dog>()这种写法,难道不奇怪吗?我们自己实现的filterTyped()就不允许这种写法。因此或许(只是猜测)filterIsInstance()的设计正是为了规避上述问题(它使用*代替了额外的T参数)。

另一个例子,基于已有的reduce()函数。我们有如下函数定义:

operator fun Animal.plus(other: Animal): Animal

(不用纠结逻辑合理性)

现在对Dog列表执行reduce操作看起来非常直接:

dogs.reduce { acc, item -> acc + item } // 编译报错

但实际上无法编译,因为编译器不知道如何将S正确推断为Animal。我们无法仅单独指定S,甚至提前指定返回类型也没用:

val animal: Animal = dogs.reduce { acc, item -> acc + item } // 编译报错

我们需要使用不太优雅的变通方案:

dogs.reduce<Animal, Dog> { acc, item -> acc + item }
(dogs as List<Animal>).reduce { acc, item -> acc + item }
dogs.reduce { acc: Animal, item: Animal -> acc + item }

内容的提问来源于stack exchange,提问作者goshki

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.30 12:18:00