考虑以下示例:
class Foo<T>
fun <T> Foo<Iterable<T>?>.bar(i: Iterable<T>) {
...
}
类Foo
有一个扩展方法bar
,它需要一个Iterable<T>?
的接收者
请考虑以下用例:
val x = listOf(123, 456)
val f = Foo<Iterable<Int>>()
f.bar(x)
f
上的编译器错误:
类型不匹配
必填:Foo
?> 发现:Foo
>
我可以理解这无法正常工作,试图将可为null的类型传递给不可为空的接收器,但是我不明白为什么不能将不可为null的类型传递给可为空的接收器。
基本上,我想说的是“接收者可能为空,但是在这种情况下,我可以保证它不是”
关于如何解决此问题的任何想法,因此扩展方法将同时允许可空和不可空类型?
注意:我无法将val f = Foo<Iterable<Int>>
更改为val f = Foo<Iterable<Int>?>
,因为这是根据属性的类型自动确定的。
答案 0 :(得分:3)
只需添加out
修饰符,它将起作用:
class Foo<out T>
我们使用out
修饰符表示协方差(类似于Java中的“?extended T”)。 协方差-是将通用类型参数从类更改为其父级之一的功能,即将List<String>
分配给List<Any>
。
以下是有关generics的文档。
答案 1 :(得分:3)
替代@Sergey的答案,您可以使用use-site variance而不是declaration-site variance(因为class Foo<out T>
会限制T
的使用并影响所有用法)并添加{扩展程序声明中的{1}}修饰符:
out
fun <T> Foo<out Iterable<T>?>.bar(i: Iterable<T>) { /* ... */ }
修饰符意味着扩展名不仅将完全接受out
,而且还将接受任何类型为Foo<Iterable<T>?>
的可空Iterable
的子类型,例如{{1} },T
,并且由于非null类型被视为可为null的子类型,因此扩展名将接受List<T>?
类型的非null对应。
答案 2 :(得分:1)
您可以达到Hotkey和Sergey这样描述的相同效果:
fun <T, S: Iterable<T>?> Foo<S>.bar(i: Iterable<T>) { /* ... */ }
它是多余的,但可能更容易理解。由于您将Iterable<T>?
定义为S
的上限,因此现在也允许包括Iterable<T>
的任何子类型。
理解了这个概念之后,我仍然会选择out Iterable<T>?
,因为它更简洁。