具有通用类型的Kotlin扩展功能取决于接收者类型参数(无需明确指定)

时间:2019-05-27 14:18:42

标签: generics kotlin kotlin-extension

有时候,我想对泛型类型使用扩展函数,该泛型类型的参数或返回值是给定类型参数的子类型,但不必指定该实际类型参数。 (也许,如果不清楚,代码会更好地说明它?)

使用this answer的签名:

inline fun <reified U : T, T> Iterable<T>.partitionByType(): Pair<List<U>, List<T>>

它迫使我按如下方式使用它:

val list : List<SomeType>
list.partitionByType<InterestedType, SomeType>()

但是,我想写的是以下内容:

list.partitionByType<InterestedType>()

其中InterestedType是列表的类型参数的子类型,即SomeType。然后,以上两种解决方案都应该给我Pair<List<InterestedType>, List<SomeType>>作为返回值。

但这实际上是不可能的。我想到的最好的签名如下:

 inline fun <reified U : Any> Iterable<Any>.partitionByType(): Pair<List<U>, List<Any>>

但是随后我们丢失了实际已知的类型T / SomeType。仍然可以在调用方进行未经检查的转换,但这不是我要的。

所以我的问题是:这可能吗?是否在未指定U : T的地方放置了T之类的东西?还是在这方面有什么计划(可以使用KEEP或Kotlin问题)?

对我来说,这听起来像是一项合理的功能,至少只要它仅适用于扩展功能即可。如果不是,那我现在可能会忽略/忽略的问题是什么? 我并不是真正想要的是一种解决方法(例如具有中间类型),但是也可以将其作为答案。

我想到的越多。如果将泛型类型的扩展函数声明如下,会更正确吗?

fun SomeType<T>.extended()

代替

fun <T> SomeType<T>.extended()

因为:如果我受SomeType<T>的控制并在其中添加该函数,则不需要任何通用类型信息来声明它,例如那么下面的内容就足够了:

fun extended()

与具有自己的通用类型的函数相同,例如:

fun <U : T> extended2()

将其添加为扩展功能现在可以强制添加T通用类型,尽管我们希望对其应用的类型显然是SomeType<T>

fun <T, U: T> SomeType<T>.extended2()

2 个答案:

答案 0 :(得分:1)

注意:这仅是“中间类型”解决方案部分的自我解答。我仍然在寻找答案,如果没有这种解决方法可以解决该问题,或者有计划支持它。

用于解决该问题的中间类型示例如下:

class PartitionType<T>

inline fun <reified T> type() = PartitionType<T>()
inline fun <reified U : T, T> Iterable<T>.partitionBy(type: PartitionType<U> = type()): Pair<List<U>, List<T>> {
    val first = ArrayList<U>()
    val second = ArrayList<T>()
    for (element in this) {
        if (element is U) first.add(element)
        else second.add(element)
    }
    return Pair(first, second)
}

这种方式可以写:

list.partitionBy(type<InterestedType>())

这将导致返回Pair<List<InterestedType>, List<SomeType>>返回值,但需要添加该类型无论如何都不会使用(间接除外;在这里inline class可能有意义,但是...应该该参数是吗?)。

这里的关键是仅将感兴趣的类型作为参数传递,而不是将其指定为请求的泛型类型(partitionBy<InterestedType, SomeType>()仍然可以使用(或更正确地说:仍然需要))。

也许我用错了字,这就是为什么我找不到合适的东西的原因。也许这只是我想要拥有的东西,没有其他人吗?至少对我来说,听起来好像可以/应该起作用。

另一种变体是在已知类型上显式指定所有扩展功能,但随后我们需要注意平台冲突,例如:

@JvmName("partitionSomeTypeByType") // only needed if we require more partitionByType-functions with differing generic type parameters
inline fun <reified U : SomeType> Iterable<SomeType>.partitionByType() = partitionBy<U, SomeType>()

但同样只有一种解决方法。

答案 1 :(得分:1)

另一个不太优雅的解决方案是返回一个包装器对象(同样是没有意义的),因为它不是通用的,并且每个函数都需要样板,因此比通过type<T>()更糟糕。

但是,通过使用inline类,可以在没有任何运行时开销的情况下实现它,因此无论如何我都将其张贴在这里:

fun main() {
    println(listOf<Number>(1, 2.0, 3f).doPartition.byType<Int>())
}

inline class PartitionByTypeWrapper<T>(val list: List<T>) {
    inline fun <reified U : T> byType(): Pair<List<U>, List<T>> {
        val (us, ts) = list.partition { it is U }
        @Suppress("UNCHECKED_CAST")
        return us as List<U> to ts
    }
}

inline val <T> List<T>.doPartition get() = PartitionByTypeWrapper(this)