4

KClass定义为public interface KClass<T : Any> : KDeclarationContainer, KAnnotatedElement, KClassifier

这很棘手,因为 a 的类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)
}

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

4

2 回答 2

3

您的问题缺少最重要的信息。您展示了一个人为地调用您的函数的情况,myFun<String?>()但是如果是这种情况,您显然可以将其更改为不使用可为空的类型。所以这可能不是真正的用例。您过度简化了您的解释并删除了我们需要回答您的问题的最相关信息:“完整的方法签名是什么以及调用站点是什么样的?

缺少的是您如何推断您的类型T?您可以从返回值、方法参数或通过在每个调用站点中显式声明它来获取它。因此,您可以在函数中使用这些选项T: Any,并决定哪个最好取决于您在问题中未显示的信息。

所以这里是你的选择:

  1. 如果您根据返回参数推断类型,则允许返回可为空,但不要使具体类型可为空:

    // 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
        // ...
    }
    
  2. 或者,如果您从传入参数推断它,请在此处执行相同的技巧:

    // 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
        // ...
    }
    
  3. 或者您实际上是在指定泛型参数(当您键入参数名称时,不要让它为空):

    // call site, any non-nullable generic parameter
    doSomething<String>()
    
    // function
    inline fun <reified T: Any> doSomethingElse() {
        val x: KClass<T> = T::class
        // ...
    }
    
  4. 或者如果您不能更改通用参数(但为什么不能?!?),请使用星形投影:

    // 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属性。yKClass

这些示例中有四分之三可以满足您的需求,而且我无法想象其中一个不起作用的情况。否则,您如何推断类型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 仍将对其进行类型检查并正常运行。但是必须检查对 invoke 的调用,因为它总是被假定为可为空的。您不能同时拥有这两种方式,想要控制可空性T但在内部将其用作类型KClass,但您可以将其用作KClass<*>取决于您从KClass. 你可能不会错过任何重要的东西。你没有展示你打算用它做什么,KClass所以很难就这个话题说更多。 KClass如果您认为它们可能会传递给您一个泛型类,通常不是一个好的类型,您应该使用 aKType来代替。

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

于 2018-04-03T19:16:40.570 回答
2

注意: 当问题没有用足够的信息来说明真正的目标是什么时,这是一个答案。暂时离开这里,以防其他人来这里寻找“其他”东西。

Nullability 是KTypenot of的一部分,KClass因此您希望KType从 reified 参数创建一个。这样做并不容易,这个概念已经在KT-15992中讨论过。Alexander Udalov 写的那个问题中有一些原型代码,但它可能已经过时并且绝对不包括可空性。

如果您将 reified 参数转换为 a 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.


实验完整解决方案:

KType我根据 Alexander Udalov 和 Ruslan Ibragimov 的示例代码创建了一个实验性的实现来推断具有可空性的完整内容。您可以在Klutter 库源代码中看到此代码,它在 Klutter 版本 2.5.3 中发布,其中标有实验性警告。

您可以查看测试代码以了解它是如何工作的,但基本上很简单:

val nullableString = reifiedKType<String?>()
println(nullableString.isMarkedNullable) // true

val nonNullableString = reifiedKType<String>()
println(nonNullableString.isMarkedNullable) // false
于 2018-04-03T13:25:25.217 回答