Kotlin 的 in 和 out 说起
这期是码上开学 Kotlin 系列的独立技术点部分的第一期,我们来聊一聊泛型。
提到 Kotlin 的泛型,通常离不开 in
和 out
关键字,但泛型这门武功需要些基本功才能修炼,否则容易走火入魔,待笔者慢慢道来。
下面这段 Java 代码在日常开发中应该很常见了:
☕️
List textViews = new ArrayList();
其中 List
表示这是一个泛型类型为 TextView
的 List
。
那到底什么是泛型呢?我们先来讲讲泛型的由来。
现在的程序开发大都是面向对象的,平时会用到各种类型的对象,一组对象通常需要用集合来存储它们,因而就有了一些集合类,比如 List
、Map
等。
这些集合类里面都是装的具体类型的对象,如果每个类型都去实现诸如 TextViewList
、ActivityList
这样的具体的类型,显然是不可能的。
因此就诞生了「泛型」,它的意思是把具体的类型泛化,编码的时候用符号来指代类型,在使用的时候,再确定它的类型。
前面那个例子,List
就是泛型类型声明。
既然泛型是跟类型相关的,那么是不是也能适用类型的多态呢?
先看一个常见的使用场景:
☕️
TextView textView = new Button(context);
// ? 这是多态
List buttons = new ArrayList();
List textViews = buttons;
// ? 多态用在这里会报错 incompatible types: List cannot be converted to List
我们知道 Button
是继承自 TextView
的,根据 Java 多态的特性,第一处赋值是正确的。
但是到了 List
的时候 IDE 就报错了,这是因为 Java 的泛型本身具有「不可变性 Invariance」,Java 里面认为 List
和 List
类型并不一致,也就是说,子类的泛型(List
)不属于泛型(List
)的子类。
Java 的泛型类型会在编译时发生类型擦除,为了保证类型安全,不允许这样赋值。至于什么是类型擦除,这里就不展开了。
你可以试一下,在 Java 里用数组做类似的事情,是不会报错的,这是因为数组并没有在编译时擦除类型:
☕️ TextView[] textViews = new TextView[10];
但是在实际使用中,我们的确会有这种类似的需求,需要实现上面这种赋值。
Java 提供了「泛型通配符」 ? extends
和 ? super
来解决这个问题。
Java 中的 ? extends
在 Java 里面是这么解决的:
☕️ List buttons = new ArrayList(); ? List
其实是List list = buttons; Object obj = (0); (obj); // ? 这里还是会报错
和前面的例子一样,编译器没法确定 ?
的类型,所以这里就只能 get
到 Object
对象。
同时编译器为了保证类型安全,也不能向 List
中添加任何类型的对象,理由同上。
由于 add
的这个限制,使用了 ? extends
泛型通配符的 List
,只能够向外提供数据被消费,从这个角度来讲,向外提供数据的一方称为「生产者 Producer」。对应的还有一个概念叫「消费者 Consumer」,对应 Java 里面另一个泛型通配符 ? super
。
Java 中的 ? super
先看一下它的写法:
☕️
?
List