Android-Kotlin:小白都能看懂的—KT泛型入门教程

我:你的招聘需求可以用这样的代码描述:

// 注意这里
// 女朋友需要一个大学(变量声明) ↓
lateinit var university: University

// 注意这里
// 我随便推荐一个大学 ↓
university = University(“某大学”)
val student: Student = university.get()// 招聘

女朋友:原来 Kotlin 也没那么难……

女朋友:能赋值一个"女子大学"吗?

我:不行,会报错。

// 注意这里
// ↓
lateinit var university: University
// 这是报错的原因
// ↓
university = University(“女子大学”)
val student: Student = university.get()

// 编译器报错!!
/*
Type mismatch.
Required: University
Found: University
*/

女朋友:什么鬼。。。

我:虽然 Student 和 FemaleStudent 之间是父子关系,但是 University 和 University 之间没有任何关系。这叫泛型的不变性。

女朋友:这不合理!女子大学招聘出来的学生,难道就不是学生?

我:招聘当然符合逻辑,但别忘了 University 还有一个 put 方法。

我:你怎么防止别人把一个男学生放到女子大学里去?

我:让我们看看如果可以将“女子大学”当作“普通大学”用,会出现什么问题:

// 声明的类型是:普通大学,然而,实际类型是:女子大学。
// ↓ ↓
var university: University = University(“女子大学”)

val maleStudent: Student = Student()
// 男学生被放进女子大学!不合理。
university.put(maleStudent)

女朋友:明白了,原来这就是泛型不变性的原因,确实能避免不少麻烦。

// 默认情况下,编译器只允许这么做
// 声明的泛型参数与实际的要一致
↓ ↓
var normalUniversity: University = University

↓ ↓
var wUniversity: University = University


3. 搞定招聘:泛型的协变(Covariant)

女朋友:如果我把 University 类里面的 put 方法删掉,是不是就可以用“女子大学”赋值了?这样就不用担心把男学生放到女子大学的问题了。

我:这还不够,还需要加一个关键字 out 告诉编译器:我们只会从 University 类往外取,不会往里面放。这时候,University 就可以当作 University 的子类。

我:这叫做泛型的协变

open class Student()
class FemaleStudent: Student()

// 看这里
// ↓
class University(val name: String) {
fun get(): T { … }
}

女朋友:我试试,果然好了!

// 不再报错
var university: University = University(“女子大学”)
val student: Student = university.get()

我:你不来写代码真浪费了。


4. 填志愿的故事:泛型的逆变(Contravariant)

女朋友:我妹妹刚高考完,马上要填志愿了,你给推荐个大学吧。

我:咱刚看过泛型协变,要不你试试自己解决这个填志愿的问题?正好 University 里有个 put 方法,你就把 put 当作填志愿就行了。

女朋友:那我依葫芦画瓢试试…… 给我妹妹报一个女子大学。

open class Student()
class FemaleStudent: Student()

class University(val name: String) {
fun get(): T { … }
// 往里放,代表填志愿
fun put(student: T){ … }
}

val sister: FemaleStudent = FemaleStudent()
val university: University = University(“女子大学”)
university.put(sister)//填报女子大学

女朋友:完美!

我:厉害。

女朋友:能不能再报一个普通综合大学?

我:不行,你忘记泛型不变性了吗?

val sister: FemaleStudent = FemaleStudent()
// 报错原因:声明类型是:女子大学 赋值的类型是:普通大学
// ↓ ↓
val university: University = University(“普通大学”)
university.put(sister)

// 报错
/*
Type mismatch.
Required: University
Found: University
*/

女朋友:我妹能报女子大学,居然不能报普通的综合大学?这不合理吧!

我:你别忘了 University 还有一个 get 方法吗?普通综合大学 get 出来的可不一定是女学生。

女朋友:哦。那我把 get 方法删了,再加个关键字?

我:对。删掉 get 方法,再加一个关键字:in 就行了。它的作用是告诉编译器:我们只会往 University 类里放,不会往外取。这时候,University 就可以当作 University 的子类。

我:这其实就叫做泛型的逆变,它们的继承关系反过来了。

// 看这里
// ↓
class University(val name: String) {
fun put(student: T){ … }
}

val sister: FemaleStudent = FemaleStudent()
// 编译通过
val university: University = University(“普通大学”)
university.put(sister)

女朋友:泛型还挺有意思。

我:上面提到的协变逆变。它们都是通过修改 University 类的泛型声明实现的,所以它们统称为:声明处型变,这是 Kotlin 才有的概念,Java 中没有。


5. 使用处型变(Use-site Variance)

女朋友:万一 University 是第三方提供的,我们无法修改,怎么办?能不能在不修改 University 类的前提下实现同样的目的?

我:可以,这就要用到使用处型变了。他们也分为:使用处协变使用处逆变

open class Student()
class FemaleStudent: Student()

// 假设 University 无法修改
class University(val name: String) {
fun get(): T { … }
fun put(student: T){ … }
}

5-1 使用处协变

我:在泛型的实参前面增加一个 out 关键字,代表我们只会从 University 往外取,不会往里放。这么做就实现了 使用处协变

// 看这里
// ↓
fun useSiteCovariant(university: University) {
val femaleStudent: Student? = university.get()

// 报错: Require Nothing? found Student?
// university.put(femaleStudent)
}

女朋友:这也挺容易理解的。那使用处逆变呢?加个 in

5-2 使用处逆变

我:对。在泛型的实参前面增加一个 in 关键字,代表我们只会从 University 往里放,不会往外取。这么做就实现了 使用处逆变

// 看这里
// ↓
fun useSiteContravariant(universityIn: University) {
universityIn.put(FemaleStudent())

// 报错: Require FemaleStudent? found Any?
// val femaleStudent: FemaleStudent? = universityIn.get()
}

女朋友:思想是一样的。

女朋友:如果是从 University 招聘学生,就是往外取,这种情况下就是协变,可以用 University 替代 University,因为女子大学取出来的女学生,和普通大学取出来的学生,都是学生。

女朋友:如果是 University 要招生,就是往里放,这种情况下,就只能用 University 替代 University,因为普通大学的招生范围更广,女子大学能接收的学生,普通大学也接收。

我:你总结的真好。顺便提一句:Kotlin 的使用处型变,还有个名字叫:类型投影(Type Projections),这名字真烂。

以上代码的具体细节可以看我这个 GitHub Commit

5-3 Kotlin 和 Java 对比

我:既然你 Kotlin 泛型理解起来毫无压力,那我再给你给加个餐,对比一下 Java 的使用处型变

女朋友:呃…… Java 是啥玩意?

我:没事,你就当看个乐呵。

使用处协变使用处逆变
KotlinUniversityUniversity
JavaUniversity<? extends Student>University<? super FemaleStudent>

我:是不是简单明了?

女朋友:还是 Kotlin 的容易理解:out 代表只能往外取(get),in代表只能往里放(put)。

我:没错。

女朋友:对比起来,Java 的表达方式真是无力吐槽。(-_-)

// Java 这辣鸡协变语法
// ↓
University<? extends Student> covariant = new University(“女子大学”);
Student student = covariant.get();
// 报错
covariant.put(student);

// Java 这辣鸡逆变语法
// ↓
University<? super FemaleStudent> contravariant = new University(“普通大学”);
contravariant.put(new FemaleStudent())
// 报错
Student s = contravariant.get();

以上代码的具体细节可以看我这个 GitHub Commit


6. Kotlin 泛型实战

我:这里有一个 Kotlin 的 Demo,要不你来看看有哪些地方能用泛型优化的?

女朋友:过分了啊!你让我学 Kotlin 就算了,还想让我帮你写代码?

女朋友:你来写,我来看。

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

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

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

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
img

最后

由于文章篇幅原因,我只把面试题列了出来,详细的答案,我整理成了一份PDF文档,这份文档还包括了还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 ,帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习。

hTnM6PW-1710505300894)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
[外链图片转存中…(img-LTXMo53y-1710505300895)]

最后

由于文章篇幅原因,我只把面试题列了出来,详细的答案,我整理成了一份PDF文档,这份文档还包括了还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 ,帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习。

需要的朋友可以私信我【答案】或者点击这里免费领取

  • 7
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值