KClass
定义为public interface 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
问题的关键是要问:如何使用test1
的编译时行为(和安全性)来获取test2
的运行时行为?
编辑: 该问题的最后一个附录是另一个例子,它证明了获得可空类的类的问题。
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>
,这一切都有效(这证明没有运行时问题,只有编译时间)。这最终是我选择的解决方法,但如果我能够保持正确的类型,那就太好了。
答案 0 :(得分:2)
注意: 如果没有说明问题并且有足够的信息来了解真正的目标是什么,这就是答案。现在离开这里以防其他人来这里寻找&#34;其他&#34;的事情。
可空性属于KType
而非KClass
的一部分,因此您希望从已知参数创建KType
。这样做并不容易,这个概念已经在KT-15992中讨论过了。亚历山大·乌达洛夫(Alexander Udalov)在该问题中提到了一些原型代码,但它可能已经过时,绝对不包括可空性。
如果您将reified参数转换为KClass
,那么您将始终失去可为空性信息,因此KClass
周围的解决方案本身无法获得您想要的效果。
似乎有一种解决方法可以单独获取可为空性信息,您可以使用KType
重建自己的T::class.createType(...)
,但这有点复杂,并且在所有情况下都未得到证实。 Ruslan Ibragimov提出的从具体T中找到可空性的解决方法如下:
inline fun <reified T : Any?> sample() {
println("Is nullable: ${isNullable<T>()}")
}
inline fun <reified T : Any?> isNullable(): Boolean {
return null is T
}
fun main(args: Array<String>) {
sample<String?>()
sample<String>()
}
// Is nullable: true
// Is nullable: false
无论采用何种方法,您仍然需要KType
。
实验性完整解决方案:
我创建了一个基于Alexander Udalov和Ruslan Ibragimov的示例代码推断出具有可为空性的完整KType
的实验性实现。您可以在Klutter library source code中看到此代码,它在Klutter 2.5.3版本中发布,并标有实验警告。
您可以查看test code以查看它是如何工作的,但基本上很简单:
val nullableString = reifiedKType<String?>()
println(nullableString.isMarkedNullable) // true
val nonNullableString = reifiedKType<String>()
println(nonNullableString.isMarkedNullable) // false
答案 1 :(得分:2)
您的问题缺乏最重要的信息。你展示了一个将你的函数调用为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?
结合使用时,请注意这一点。
根据您对问题的上次更新,这会保留您对output
函数引用所期望的可为空性,但允许它适用于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
代替。