我不确定“失败”是否正确,但这是我面临的问题。我花了相当多的时间将其重现为最小的示例,所以到这里去了:
class BaseParameterizedType<T>
fun <U: BaseParameterizedType<*>> getSpecific(clazz: KClass<in U>) : U {
TODO()
}
fun example(arg: KClass<out BaseParameterizedType<*>>)) {
getSpecific(arg.innerType)
}
好的,因此上面的代码在“TODO”处失败,但是,如果不存在并且函数正常返回,则它肯定会失败,并出现空指针异常。我尽力找出出了什么问题,所以我转向了反编译的Java代码(来自kotlin字节码):
public static final void example(@NotNull KClass arg) {
Intrinsics.checkParameterIsNotNull(arg, "arg");
getSpecific(arg.getInnerType());
throw null; // <-- The problem
}
如果我将
getSpecific(clz: KClass<in U>) : U
的函数签名更改为以下任何形式:getSpecific(clz: KClass<out U>) : U
getSpecific(clz: KClass<U>) : U
getSpecific(clz: KClass<in U>) : BaseParameterizedType<*>
甚至是
example(arg: KClass<out BaseParameterizedType<*>)
或example(arg: KClass<BaseParameterizedType<*>>)
的函数,那么生成的代码是:public static final void example(@NotNull KClass arg) {
Intrinsics.checkParameterIsNotNull(arg, "arg");
getSpecific(arg.getInnerType());
}
现在,假设在调用站点,我将其更改为:
getSpecific(BaseParameterizedType::class)
那么这也不会生成
throw null
子句。因此,我猜测这与kotlin有关,假设此强制转换将始终失败或存在不确定的信息来进行推断?因此,我们知道
arg.innerType
是KClass<out BaseParameterizedType<*>>
,并且我们在接受KClass<in BaseParameterizedType<*>>
的站点上使用它,所以U
为什么不推断为BaseParamterizedType<*>>
。从字面上看,这是唯一会匹配的类型。同时,我认为仅生成
throw null
语句难以调试。 stacktrace只会指向存在getSpecific
的行,祝您好运,找出空指针异常来自何处。 最佳答案
当所推断的类型为Nothing
(在您的情况下)时,这是一个与类型推断角案例处理有关的已知问题:
由于对投影KClass<in U>
和KClass<out BaseParameterizedType<*>>
进行了强制性尝试,因此推断的行为如此。
基本上,同时投影一个out
的类型意味着in Nothing
(因为实际的type参数可以是任何子类型,并且不能安全地传递任何内容)。因此,为了使KClass<out BaseParameterizedType<*>>
与KClass<in U>
匹配,编译器选择U := Nothing
,这意味着函数调用也返回Nothing
。
备注:Foo<out Any>
投影不能将Foo<in T>
与T := Any
匹配,因为传递给Foo<out Any>
的值的实际类型参数可以是Int
。然后,如果Foo<T>
在其某些功能中接受T
,则允许上述匹配还将使您可以将Any
实例传递到Foo<Int>
不需要它们的地方。实际上,由于in Nothing
投影类型的未知性质,因此out
成为匹配它们的唯一方法。
之后,对于返回Nothing
的函数调用,编译器将插入该throw null
字节码以确保执行不会继续(评估Nothing
型表达式is supposed to never finish correctly)。
查看问题:KT-20849,KT-18789