女朋友:这不合理!女子大学招聘出来的学生,难道就不是学生?
我:招聘当然符合逻辑,但别忘了 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 是啥玩意?
我:没事,你就当看个乐呵。
使用处协变 | 使用处逆变 | |
---|---|---|
Kotlin | University | University |
Java | University<? 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 就算了,还想让我帮你写代码?
女朋友:你来写,我来看。
我:呃……听领导的。
6-1 泛型版本的 apply 函数
我:这是上一个章节里的代码,这个 apply 函数其实可以用泛型来简化,让所有的类都能使用。
// 替代 替代 替代
// ↓ ↓ ↓
fun User.apply(block: User.() -> Unit): User{
block()
return this
}
user?.apply { this: User ->
…
username.text = this.name
website.text = this.blog
image.setOnClickListener { gotoImagePreviewActivity(this) }
}
我:使用泛型替代以后的 apply 函数就是这样:
// 泛型 泛型 泛型
// ↓ ↓ ↓ ↓
fun T.apply(block: T.() -> Unit): T{
block()
return this
}
女朋友:Kotlin 官方的 apply 函数也是这么实现的吗?
我:几乎一样,它只是多了个
contract
,你暂时还不懂。女朋友:呃……还有其他例子吗?
6-2 泛型版本的 HTML 构建器
我:在上一个章节里,我实现了一个简单的
类型安全的 HTML 构建器
,其中有不少重复的代码。女朋友:咱们可以利用泛型
消灭重复代码
,对吧?我:没错。
class Body : BaseElement(“body”) {
fun h1(block: () -> String): H1 {
val content = block()
val h1 = H1(content)
this.children += h1
return h1
}
// ↑
// 看看这重复的模板代码
// ↓
fun p(block: () -> String): P {
val content = block()
val p = P(content)
this.children += p
return p
}
}
// ↑
// 看看这重复的模板代码
// ↓
class Head : BaseElement(“head”) {
fun title(block: () -> String): Title {
val content = block()
val title = Title(content)
this.children += title
return title
}
}
我:让我们用泛型来优化:
open class BaseElement(var name: String, var content: String = “”) : Element {
// 在父类增加一个共有的泛型方法
protected fun initString(element: T, init: T.() -> String): T {
val content = element.init()
element.content = content
children.add(element)
return element
}
}
class Body : BaseElement(“body”) {
fun h1(block: H1.() -> String) = initString(H1(“h1”), block)
fun p(block: P.() -> String) = initString(P(“p”), block)
}
class Head : BaseElement(“head”) {
fun title(block: Title.() -> String) = initString(Title(), block)
}
我:还有一个地方有重复代码:
class HTML : BaseElement(“html”) {
fun head(block: Head.() -> Unit): Head {
val head = Head()
head.block()
this.children += head
return head
}
// ↑
// 看看这重复的模板代码
// ↓
fun body(block: Body.() -> Unit): Body {
val body = Body()
body.block()
this.children += body
return body
}
}
我:优化后:
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
最后
下面是有几位Android行业大佬对应上方技术点整理的一些进阶资料。希望能够帮助到大家提升技术
高级UI,自定义View
UI这块知识是现今使用者最多的。当年火爆一时的Android入门培训,学会这小块知识就能随便找到不错的工作了。
不过很显然现在远远不够了,拒绝无休止的CV,亲自去项目实战,读源码,研究原理吧!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
tp.com/2024/03/13/H4lCoPEF.jpg" />
最后
下面是有几位Android行业大佬对应上方技术点整理的一些进阶资料。希望能够帮助到大家提升技术
[外链图片转存中…(img-JPkuCfLD-1712201027051)]
高级UI,自定义View
UI这块知识是现今使用者最多的。当年火爆一时的Android入门培训,学会这小块知识就能随便找到不错的工作了。
不过很显然现在远远不够了,拒绝无休止的CV,亲自去项目实战,读源码,研究原理吧!
[外链图片转存中…(img-e8LMiWP5-1712201027052)]