【码上开学】Kotlin 的泛型(1)

  • 其中 ? 是个通配符,表示这个 List 的泛型类型是一个未知类型
  • extends 限制了这个未知类型的上界,也就是泛型类型必须满足这个 extends 的限制条件,这里和定义 classextends 关键字有点不一样:
  • 它的范围不仅是所有直接和间接子类,还包括上界定义的父类本身,也就是 TextView
  • 它还有 implements 的意思,即这里的上界也可以是 interface

这里 ButtonTextView 的子类,满足了泛型类型的限制条件,因而能够成功赋值。

根据刚才的描述,下面几种情况都是可以的:

☕️
List<? extends TextView> textViews = new ArrayList(); // 👈 本身
List<? extends TextView> textViews = new ArrayList(); // 👈 直接子类
List<? extends TextView> textViews = new ArrayList(); // 👈 间接子类

一般集合类都包含了 getadd 两种操作,比如 Java 中的 List,它的具体定义如下:

☕️
public interface List extends Collection{
E get(int index);
boolean add(E e);

}

上面的代码中,E 就是表示泛型类型的符号(用其他字母甚至单词都可以)。

我们看看在使用了上界通配符之后,List 的使用上有没有什么问题:

☕️
List<? extends TextView> textViews = new ArrayList();
TextView textView = textViews.get(0); // 👈 get 可以
textViews.add(textView);
// 👆 add 会报错,no suitable method found for add(TextView)

前面说到 List<? extends TextView> 的泛型类型是个未知类型 ?,编译器也不确定它是啥类型,只是有个限制条件。

由于它满足 ? extends TextView 的限制条件,所以 get 出来的对象,肯定是 TextView 的子类型,根据多态的特性,能够赋值给 TextView,啰嗦一句,赋值给 View 也是没问题的。

到了 add 操作的时候,我们可以这么理解:

  • List<? extends TextView> 由于类型未知,它可能是 List<Button>,也可能是 List<TextView>
  • 对于前者,显然我们要添加 TextView 是不可以的。
  • 实际情况是编译器无法确定到底属于哪一种,无法继续执行下去,就报错了。

那我干脆不要 extends TextView ,只用通配符 ? 呢?

这样使用 List<?> 其实是 List<? extends Object> 的缩写。

☕️
List buttons = new ArrayList<>();

List<?> list = buttons;
Object obj = list.get(0);

list.add(obj); // 👈 这里还是会报错

和前面的例子一样,编译器没法确定 ? 的类型,所以这里就只能 getObject 对象。

同时编译器为了保证类型安全,也不能向 List<?> 中添加任何类型的对象,理由同上。

由于 add 的这个限制,使用了 ? extends 泛型通配符的 List,只能够向外提供数据被消费,从这个角度来讲,向外提供数据的一方称为「生产者 Producer」。对应的还有一个概念叫「消费者 Consumer」,对应 Java 里面另一个泛型通配符 ? super

Java 中的 ? super

先看一下它的写法:

☕️
👇
List<? super Button> buttons = new ArrayList();

这个 ? super 叫做「下界通配符」,可以使 Java 泛型具有「逆变性 Contravariance」。

与上界通配符对应,这里 super 限制了通配符 ? 的子类型,所以称之为下界。

它也有两层意思:

  • 通配符 ? 表示 List 的泛型类型是一个未知类型
  • super 限制了这个未知类型的下界,也就是泛型类型必须满足这个 super 的限制条件。
  • super 我们在类的方法里面经常用到,这里的范围不仅包括 Button 的直接和间接父类,也包括下界 Button 本身。
  • super 同样支持 interface

上面的例子中,TextViewButton 的父类型 ,也就能够满足 super 的限制条件,就可以成功赋值了。

根据刚才的描述,下面几种情况都是可以的:

☕️
List<? super Button> buttons = new ArrayList(); // 👈 本身
List<? super Button> buttons = new ArrayList(); // 👈 直接父类
List<? super Button> buttons = new ArrayList(); // 👈 间接父类

对于使用了下界通配符的 List,我们再看看它的 getadd 操作:

☕️
List<? super Button> buttons = new ArrayList();
Object object = buttons.get(0); // 👈 get 出来的是 Object 类型
Button button = …
buttons.add(button); // 👈 add 操作是可以的

解释下,首先 ? 表示未知类型,编译器是不确定它的类型的。

虽然不知道它的具体类型,不过在 Java 里任何对象都是 Object 的子类,所以这里能把它赋值给 Object

Button 对象一定是这个未知类型的子类型,根据多态的特性,这里通过 add 添加 Button 对象是合法的。

使用下界通配符 ? super 的泛型 List,只能读取到 Object 对象,一般没有什么实际的使用场景,通常也只拿它来添加数据,也就是消费已有的 List<? super Button>,往里面添加 Button,因此这种泛型类型声明称之为「消费者 Consumer」。


小结下,Java 的泛型本身是不支持协变和逆变的。

  • 可以使用泛型通配符 ? extends 来使泛型支持协变,但是「只能读取不能修改」,这里的修改仅指对泛型集合添加元素,如果是 remove(int index) 以及 clear 当然是可以的。
  • 可以使用泛型通配符 ? super 来使泛型支持逆变,但是「只能修改不能读取」,这里说的不能读取是指不能按照泛型类型读取,你如果按照 Object 读出来再强转当然也是可以的。

根据前面的说法,这被称为 PECS 法则:「Producer-Extends, Consumer-Super」。

理解了 Java 的泛型之后,再理解 Kotlin 中的泛型,有如练完九阳神功再练乾坤大挪移,就比较容易了。

Kotlin 中的 outin

和 Java 泛型一样,Kolin 中的泛型本身也是不可变的。

  • 使用关键字 out 来支持协变,等同于 Java 中的上界通配符 ? extends
  • 使用关键字 in 来支持逆变,等同于 Java 中的下界通配符 ? super

🏝️
var textViews: List
var textViews: List

换了个写法,但作用是完全一样的。out 表示,我这个变量或者参数只用来输出,不用来输入,你只能读我不能写我;in 就反过来,表示它只用来输入,不用来输出,你只能写我不能读我。

你看,我们 Android 工程师学不会 outin,其实并不是因为这两个关键字多难,而是因为我们应该先学学 Java 的泛型。是吧?

说了这么多 List,其实泛型在非集合类的使用也非常广泛,就以「生产者-消费者」为例子:

🏝️
class Producer {
fun produce(): T {

}
}

val producer: Producer = Producer()
val textView: TextView = producer.produce() // 👈 相当于 ‘List’ 的 get

再来看看消费者:

🏝️
class Consumer {
fun consume(t: T) {

}
}

val consumer: Consumer = Consumer()
consumer.consume(Button(context)) // 👈 相当于 ‘List’ 的 ‘add’

声明处的 outin

在前面的例子中,在声明 Producer 的时候已经确定了泛型 T 只会作为输出来用,但是每次都需要在使用的时候加上 out TextView 来支持协变,写起来很麻烦。

Kotlin 提供了另外一种写法:可以在声明类的时候,给泛型符号加上 out 关键字,表明泛型参数 T 只会用来输出,在使用的时候就不用额外加 out 了。

🏝️ 👇
class Producer {
fun produce(): T {

}
}

val producer: Producer = Producer() // 👈 这里不写 out 也不会报错
val producer: Producer = Producer() // 👈 out 可以但没必要

out 一样,可以在声明类的时候,给泛型参数加上 in 关键字,来表明这个泛型参数 T 只用来输入。

🏝️ 👇
class Consumer {
fun consume(t: T) {

}
}

val consumer: Consumer = Consumer() // 👈 这里不写 in 也不会报错
val consumer: Consumer = Consumer() // 👈 in 可以但没必要

*

前面讲到了 Java 中单个 ? 号也能作为泛型通配符使用,相当于 ? extends Object。 它在 Kotlin 中有等效的写法:* 号,相当于 out Any

🏝️ 👇
var list: List<*>

和 Java 不同的地方是,如果你的类型定义里已经有了 out 或者 in,那这个限制在变量声明时也依然在,不会被 * 号去掉。

比如你的类型定义里是 out T : Number 的,那它加上 <*> 之后的效果就不是 out Any,而是 out Number

where 关键字

Java 中声明类或接口的时候,可以使用 extends 来设置边界,将泛型类型参数限制为某个类型的子集:

☕️
// 👇 T 的类型必须是 Animal 的子类型
class Monster{
}

注意这个和前面讲的声明变量时的泛型类型声明是不同的东西,这里并没有 ?

同时这个边界是可以设置多个,用 & 符号连接:

☕️
// 👇 T 的类型必须同时是 Animal 和 Food 的子类型
class Monster<T extends Animal & Food>{
}

Kotlin 只是把 extends 换成了 : 冒号。

🏝️ 👇
class Monster

设置多个边界可以使用 where 关键字:

🏝️ 👇
class Monster where T : Animal, T : Food

有人在看文档的时候觉得这个 where 是个新东西,其实虽然 Java 里没有 where ,但它并没有带来新功能,只是把一个老功能换了个新写法。

不过笔者觉得 Kotlin 里 where 这样的写法可读性更符合英文里的语法,尤其是如果 Monster 本身还有继承的时候:

🏝️
class Monster : MonsterParent
where T : Animal

reified 关键字

由于 Java 中的泛型存在类型擦除的情况,任何在运行时需要知道泛型确切类型信息的操作都没法用了。

比如你不能检查一个对象是否为泛型类型 T 的实例:

☕️
void printIfTypeMatch(Object item) {
if (item instanceof T) { // 👈 IDE 会提示错误,illegal generic type for instanceof
System.out.println(item);
}
}

Kotlin 里同样也不行:

🏝️
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

尾声

以薪资待遇为基础,以发展为最终目标,要在高薪资的地方,谋求最好的发展!

下面是有几位Android行业大佬对应上方技术点整理的一些进阶资料。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

g src=“https://i-blog.csdnimg.cn/blog_migrate/f9bdcd758a4cb399327ac9d92bd5235c.jpeg” />

尾声

以薪资待遇为基础,以发展为最终目标,要在高薪资的地方,谋求最好的发展!

下面是有几位Android行业大佬对应上方技术点整理的一些进阶资料。

[外链图片转存中…(img-VeI6vWpw-1712856508406)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值