Kotlin - 可为空的接收器扩展不接受不可为空的等效项

Kotlin - nullable receiver extension won't accept non-nullable equivalent

考虑以下示例:

class Foo<T>

fun <T> Foo<Iterable<T>?>.bar(i: Iterable<T>) {
    ...
}

Class Foo 有一个扩展方法,bar 需要 Iterable<T>?

的接收者

考虑以下用例:

val x = listOf(123, 456)
val f = Foo<Iterable<Int>>()

f.bar(x)

f 上的编译器错误:

Type mismatch

Required: Foo<Iterable<Int>?>

Found: Foo<Iterable<Int>>

我可以理解这不是反过来工作,试图将可空类型传递给不可空接收者,但我不明白为什么我不能将不可空类型传递给可空接收者。

基本上我想说的是“接收者可能为空,但在这种情况下,我可以保证它不是”

有没有想过如何解决这个问题,以便扩展方法同时允许可空和不可空类型?

注:我无法将val f = Foo<Iterable<Int>>改为val f = Foo<Iterable<Int>?>,因为这是根据属性的类型自动决定的。

只需添加 out 修饰符即可:

class Foo<out T>

我们使用out修饰符来表示协方差(类似于Java中的“?extends T”)。 Covariance - 是将泛型类型参数从 class 更改为其父项之一的能力,即将 List<String> 分配给 List<Any>

这是关于 generics 的文档。

替代@Sergey 的回答,您可以使用 use-site variance rather than declaration-site variance(因为 class Foo<out T> 将限制 T 的使用并影响所有用法)并添加 out 修饰符在扩展声明处:

fun <T> Foo<out Iterable<T>?>.bar(i: Iterable<T>) { /* ... */ }

(runnable sample)

out 修饰符意味着扩展将不仅接受完全 Foo<Iterable<T>?>,还会接受使用 T 键入的可空 Iterable 的任何子类型,例如 List<T>?Set<T>?,而且,由于非空类型被视为可空类型的子类型,扩展将接受 Iterable 类型的非空对应项。

你可以达到 Hotkey 和 Sergey 描述的相同效果:

fun <T, S: Iterable<T>?> Foo<S>.bar(i: Iterable<T>) { /* ... */ }

它比较冗余,但可能更容易理解。由于您将 Iterable<T>? 定义为 S 的上限,因此现在也允许包含 Iterable<T> 的任何子类型。

掌握概念后,我还是会选择out Iterable<T>?,因为它更简洁。