Kotlin难以上传到推断的(现场)参数
我不确定是否“失败”是正确的话,但这是我面临的问题。 我花了相当长的时间来重现这个最小的例子,所以在这里:
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<*>>
的站点上使用它,所以为什么不推断为BaseParamterizedType<*>>
。 这实际上是唯一可以匹配的类型。
同时,我认为只是生成一个throw null
语句难以调试是难以置信的。 stacktrace会指向getSpecific
行,并且找出空指针异常来自哪里。
这是一个已知的问题关于类型推理的角落案件处理时,推断类型是Nothing
(这是你的情况):
因为对KClass<in U>
和KClass<out BaseParameterizedType<*>>
的强制尝试,推理的行为就是这样。
基本上, out
-projected类型在同一时间意味着in Nothing
(因为实际的类型参数可以是任何的子类型,没有什么可以安全地传入)。 因此, KClass<out BaseParameterizedType<*>>
与KClass<in U>
匹配,编译器将选择U := Nothing
,这意味着函数调用也会返回Nothing
。
备注: Foo<out Any>
投影不能与Foo<in T>
中的Foo<in T>
Foo<out Any>
匹配, T := Any
,因为为Foo<out Any>
传递的值的实际类型实Foo<out Any>
可以是,例如Int
。 然后,如果Foo<T>
在其某些函数中接受T
,允许前面提到的匹配也允许您将Any
实例传递到Foo<Int>
不指望它们的地方。 实际上, in Nothing
成为匹配它们的唯一方法的情况下,由于out
投射类型的未知性质。
之后,对于Nothing
返回函数调用,编译器会插入那些会throw null
字节码的错误,以确保执行不会继续(评估Nothing
型表达式应该永远不会正确完成 )。
看到的问题: KT-20849 , KT-18789
就像@hotkey提到的那样, in Nothing
和Nothing
意味着Nothing
都不会抛出null。所以我做了一些这样的测试:
fun main(args: Array<String>) { tryToReturnNothing() } fun tryToReturnNothing(): Nothing{ TODO() }
生成 – >
public static final void main(@NotNull String[] args) { Intrinsics.checkParameterIsNotNull(args, "args"); tryToReturnNothing(); throw null; // here } @NotNull public static final Void tryToReturnNothing() { throw (Throwable)(new NotImplementedError((String)null, 1, (DefaultConstructorMarker)null)); }
考虑到null
的类型是Nothing?
,我们可以返回Nothing?
而不是Nothing
。 所以我把U
换成U?
,然后throw null
子句消失:
fun <U: BaseParameterizedType<*>> getSpecific(clazz: KClass<in U>) : U? { // see here: change U to U? TODO() } fun example(arg: KClass<out BaseParameterizedType<*>>) { getSpecific(arg) }
生成 – >
@Nullable public static final BaseParameterizedType getSpecific(@NotNull KClass clazz) { Intrinsics.checkParameterIsNotNull(clazz, "clazz"); throw (Throwable)(new NotImplementedError((String)null, 1, (DefaultConstructorMarker)null)); } public static final void example(@NotNull KClass arg) { Intrinsics.checkParameterIsNotNull(arg, "arg"); getSpecific(arg); }