kotlin – 以允许可空类型和使用该类型参数声明`KClass`的方式声明函数泛型类型参数

KClass被定义为公共接口KClass< T:Any> :KDeclarationContainer,KAnnotatedElement,KClassifier

这很棘手,因为String的类?应该是KClass< String>,但是不可能获得.

鉴于以下3个示例(应该完全基本上完成相同的工作),其中1个不编译,其他返回相同的运行时类型.

inline fun <reified T> test1(): Any = T::class
inline fun <reified T: Any> test2(): KClass<T> = T::class
inline fun <reified T> test3(): KClass<T> = T::class // does not compile

test1<String?>() // class kotlin.String
test1<String>() // class kotlin.String
test2<String?>() // does not compile
test2<String>() // class kotlin.String

问题的关键是:如何使用test2的编译时行为(和安全性)来获取test1的运行时行为?

编辑:
这个问题的最后一个附录是另一个例子,它证明了获得可空类型的问题.

inline fun <reified T> test4() {
    val x = T::class // compiles, implied type of x is KClass<T>
    val y: KClass<T> = T::class // does not compile with explicit type of KClass<T>
}

我特别遇到问题的呼叫站点是这样的:

class OutputContract<T>(
    private val output: () -> T,
    val outputType: KClass<T>   // ERROR!
) {    
    fun invoke(): T {
        return output()
    }
}


inline fun <reified T> output(noinline output: () -> T): OutputContract<T> {
    return OutputContract(output, T::class)
}

这里唯一的错误是KClass< T>,而不是T :: class,它很好.我想允许使用者将可空性指定为合同的一部分,因此添加Any约束将不起作用.如果我只是转KClass< T>进入KClass< Any>,这一切都有效(证明没有运行时问题,只有编译时).这最终是我选择的解决方法,但如果我能够真正保持正确的类型,那就太好了.

最佳答案
你的问题缺乏最重要的信息.你展示了一个人为调用你的函数作为myFun< String?>()的情况,但如果是这种情况你可以明显地将它改为不使用可空类型.所以这可能不是真正的用例.您过度简化了解释并删除了我们回答您问题所需的最相关信息:“完整方法签名是什么以及呼叫网站的外观是什么?”

缺少的是你如何提出你的T型?您可以从返回值,方法参数中获取它,也可以在每个调用站点中明确说明它.因此,您可以选择在您的函数中使用T:Any,并确定哪个最佳取决于您未在问题中显示的信息.

所以这是你的选择:

>如果基于return参数推断类型,则允许返回可为空,但不要使reified类型为空:

// call site, any return type nullable or not
val something: String? = doSomething()

// function
inline fun <reified T: Any> doSomething(): T? {
    val x: KClass<T> = T::class
    // ...
}

>或者,如果您从传入参数推断它,请在那里执行相同的操作:

// call site, any parameter type nullable or not
val param: String? = "howdy"
doSomethingElse(param)

// function
inline fun <reified T: Any> doSomethingElse(parm: T?) {
    val x: KClass<T> = T::class
    // ...
}

>或者您实际上是指定泛型参数(只需在键入参数名称时不使其可为空):

// call site, any non-nullable generic parameter
doSomething<String>()

// function
inline fun <reified T: Any> doSomethingElse() {
    val x: KClass<T> = T::class
    // ...
}

>如果你不能改变通用参数,可以使用星形投影(但为什么不能改变?!?):

// call site: whatever you want it to be

// function:
inline fun <reified T> test4() {
    val x = T::class // compiles, implied type of x is KClass<T>
    val y: KClass<*> = T::class  KClass<T>
}

顺便说一下,x和y的行为都是一样的,KClass引用上会缺少一些方法/属性.

这些例子中有四分之三会让你想要你,而我无法想象其中一个例子不起作用的情况.否则,你如何推断​​T型?什么是与上述不兼容的模式?

使用< T:Any>注意这个技巧结合T?在相同的方法签名.

根据您对问题的上次更新,这保持了您对输出函数引用的预期可空性,但允许它适用于KClass:

class OutputContract<T: Any>(private val output: () -> T?, val outputType: KClass<T>) {
    fun invoke(): T? {
        return output()
    }
}

inline fun <reified T: Any> output(noinline output: () -> T?): OutputContract<T> {
    return OutputContract(output, T::class)
}

用户仍然可以通过传递不返回空值的输出实现来控制可空性,Kotlin仍然会对它们进行类型检查并且正常运行.但是必须检查调用调用,因为它总是被认为是可空的.你不能真正拥有它两种方式,想要对T进行可空性控制,但在内部使用它作为类型化的KClass,但你可以将它用作KClass< *>取决于您使用KClass的功能.你可能没有遗漏任何重要的东西.您没有显示您打算如何处理KClass,因此很难对该主题进行更多说明.如果您认为KClass可能会传递给您一个泛型类,那么它通常不是一个好的类型,您应该使用KType.

Image from Understanding Generics and Variance in Kotlin
图片来自Understanding Generics and Variance in Kotlin

转载注明原文:kotlin – 以允许可空类型和使用该类型参数声明`KClass`的方式声明函数泛型类型参数 - 代码日志