本篇文章将长期记录并解答笔者在学习使用 Kotlin 过程中所遇到的问题点,参考资料会以 扔物线 老师的 码上开学系列教程 和 Kotlin 官方教程 为主,同时也会配合参考其他优秀文章。
1 单例模式
Java 的单例模式通常实现起来稍显繁琐,包含大量的模板代码,具体使用可参考这篇文章。
而 Kotlin 实现单例模式是非常便捷的,只需使用 object 关键字即可。
1 | // 👇 class 替换成了 object |
通过 Android studio 转换工具将上面代码转成 Java 代码如下:
1 | public final class A { |
可以看到,实际上这种通过 object 实现的单例是一个饿汉式的单例,并且实现了线程安全。
2 代码块
Java 一共有四大代码块,可参考这篇文章,需要注意的是,文章中最后提到的同步代码块中对静态代码块的解释有误,如果要了解 java 中的同步代码块知识,可以移步到笔者之前写的这篇文章。
Kotlin 中的构造代码块和静态代码块有了一些变化,先来看构造代码块。
Java 是这样写的:
1 | public class User { |
而 Kotlin 是这样写的:
1 | class User { |
对于静态代码块, Java 是这样的:
1 | public class Sample { |
而 Kotlin 是这样写的:
1 | class Sample { |
3 访问限制符
Java 的访问限制符可以参考这篇文章访问控制修饰符部分。其中,如下几个知识点需要注意一下:
1、子类与基类在同一包中:被声明为 protected 或 default 的变量、方法和构造器能被同一个包中的任何其他类访问。
2、子类与基类不在同一包中:那么在子类中,子类实例可以访问其从基类继承而来的 protected 方法,而不能访问基类实例的 protected 方法。同时,在子类中既不能访问其从基类继承而来的 default 方法,也不能访问基类实例的 default 方法。(变量、构造器同理。)
3、子类可以对从父类继承的方法加宽访问范围。访问控制符的访问范围有大到小排序是:public > protect > default > private。
对于Kotlin的访问控制符,直接参考码上开学吧。
4 内联函数
首先我们来看使用内联函数的一个例子:
1 | inline fun log() { |
反编译成 Java 的代码如下:
1 | public static final void log() { |
而我们去掉 inline 关键字后,再反编译成 Java 代码,如下:
1 | public static final void log() { |
如上可以看到,使用内联函数可以减少一层函数的调用栈,但如果有多个地方调用内联函数的话,就会隐式地增加编译后代码的行数。那么如果正确使用内联函数呢,或者说它到底有什么用?有如下几个使用场景:
1、函数参数包含函数类型的函数。
1 | class View { |
如果不使用内联函数,由于 setOnClickListener 使用的是函数类型的参数,那么在调用时会产生一个额外的对象,具体的过程可以反编译成 Java 文件后看看。
2、泛型具体化。
平时我们使用泛型时,是不能直接对泛型类型进行操作的。比如下面的操作会导致编译不通过:
1 | fun <T> excute() { |
大意是:不能使用泛型 T 作为具体的类型参数。如果依照 Java 的解决方式,可以这样写:
1 | fun <T> excute(class: Class<T>) { |
不过, Kotlin 中有更加方便的用法,也就是使用 inline 关键字达到让泛型具体化的目标:
1 | inline fun <reified T> excute() { |
Retrofit 最新拓展中,内部已经实现了一个内联函数用来代替旧版本创建实例的方式。具体可参考 Retrofit.create() 拓展方法。
5 委托
参考地址>>
上面的文章中,笔者认为只需要理解从开篇到“把属性储存在映射中”部分的内容,剩下的内容读者自行斟酌其重要性吧。
6 构造器执行顺序
Kotlin 的构造器执行顺序和 Java 一致,可参考这篇文章>>
7 作用域函数
返回自身
从apply和also中选
1.作用域中使用 this 作为参数,选择 apply。
2.作用域中使用 it 作为参数,选择 also。不需要返回自身
从run和let中选择
1.作用域中使用 this 作为参数,选择 run。
2.作用域中使用 it 作为参数,选择 let。
apply 适合对一个对象做附加操作的时候。let 适合配合空判断的时候。with 适合对同一个对象进行多次操作的时候。它的最大的作用是可以返回任何对象,这是其它几个作用域函数不能实现的。官方详解>>
8 lambda表达式、匿名函数以及函数类型三者之间的联系与区别
9 理解「带接收者类型的函数类型 」
概念:官方文档(只需看相关的)>>
实践(来自 Android KTX Core):SharedPreferences 源码>>
10 闭包
其实闭包并不是 Kotlin 中的新概念,在 Java 8 中就已经支持。我们以 Thread 为例,来看看什么是闭包:
1 | // 创建一个 Thread 的完整写法 |
形如 Thread {…} 这样的结构中 {} 就是一个闭包。
在 Kotlin 中有这样一个语法糖:当函数的最后一个参数是 lambda 表达式时(又名:拖尾 lambda 表达式),可以将 lambda 写在括号外。这就是它的闭包原则。
在这里需要一个类型为 Runnable 的参数,而 Runnable 是一个 Java 接口,且只定义了一个函数 run,这种情况可以转换成传递一个 lambda 表达式(第二段),因为是最后一个参数,根据闭包原则我们就可以直接写成 Thread {…}(第三段) 的形式。
注意:
Kotlin 对于“函数式接口可以使用 lambda 表达式”这个用法仅仅是针对接口是 Java 类型的情况,如果接口是 Kotlin 写的,则在 Kotlin 中调用以接口作为参数的方法时必须通过关键字’object’创建匿名类的对象来作为其参数。
11 平台类型
在 kotlin 中使用其他平台(如:Java)的类型时,如果该类型未用可空性注解标注,则 Kotlin 会自动将这其解释成平台类型。
在类型后面加上一个感叹号的类型就是平台类型,平台类型不能手动声明。
Kotlin 对平台类型的空检测会放宽, 因此它们的安全保证与在 Java 中相同,即要空检查。
12 协程
使用请参考 码上开学。
源码解析请参考如下两篇文章:
1、Kotlin Primer·第七章·协程库(上篇);
2、Kotlin Primer·第七章·协程库(中篇)。
另外,为了加深源码以及使用的理解,可配合下面知乎的三篇文章阅读:
1、Kotlin协程启动模式;
2、Kotlin协程调度;
3、Kotlin协程生命的尽头—协程取消。
协程 + ViewModel 使用案例:
1、Retrofit加kotlin协程为何如此优雅;
2、Retrofit+协程+Jetpack架构组件实现简单网络请求。
PS:链接2的文章原标题提到 MVVM ,此表述有误。 MVVM 中的 ViewModel 和官方提供的 ViewModel 本质上是不同的概念。
源码阅读建议
首先,通过如下两篇文章了解Lifecycle的使用:
文章1
文章2
之后,依据 LiveData、ViewModel、Retrofit 以及协程之间搭配使用的案例,可以仔细的去看下源码(还是很好理解的),这对于加深理解很有帮助。主要侧重几个点:
1、LiveData 是如何监听数据改变的?setValue 和 postValue 的区别?
2、ViewModel 为什么能在屏幕发生旋转后,仍然能够拿到原来的 ViewModel 实例?
3、ViewModel 是如何与界面的生命周期绑定的?
4、viewModelScope 为什么能在界面销毁时自动取消任务?
5、Retrofit 2.6 为什么能与协程一起工作?
6、Retrofit 使用协程以后如何在界面销毁自动取消请求?