前言
2019 年 5 月 8 日,谷歌宣布,Kotlin 编程语言现在是 Android 应用程序开发人员的首选语言。
Kotlin语言对于Android开发人员的重要程度不言而喻。 然而在项目开发过程中,我仍然发现很多同事在使用kotlin开发过程中,受 “java后遗症” 荼毒太深,有些很适合使用kotlin语法糖或者特性的地方,都选择的比较保守的实现方式。
所以,当当当:

我自己截取了个人认为日常开发中最常用、易理解的几个kotlin场景,这些场景适合已经kotlin入门,但是又希望能够进一步优化自己kotlin代码的童鞋。对于一些众所周知的空安全属性等则没有进行描述。
文中代码含有改造前和改造后的代码,并且做了注释,方便大家对比。
希望能帮助到大家:
单表达式函数
当函数返回单个表达式时,可以省略花括号并且在 = 符号之后指定代码体即可:
//改造前
fun double(x: Int): Int {
return x * 2
}
//改造后
fun double(x: Int): Int = x * 2
使⽤作⽤域函数
作用域函数顾名思义,就是形成一个临时的作用域,在此作用域中,可以更方便地访问该对象。
作用域函数共五种: let 、run、with、apply以及also。
这些作用域函数在本质上都非常的相似,区别仅在于引用对象的方式和返回值。在此就不一一做介绍,只举apply的一个例子。
apply可以使对象作为接收者( this )来访问。并且返回值为对象本身。
//改造前
var rectangleB = Rectangle()
rectangleB.length = 4
rectangleB.breadth = 5
rectangleB.color = 0xFAFAFA
rectangleB.padding = 2
//改造后
var rectangleB = Rectangle().apply {
length = 4
breadth = 5
color = 0xFAFAFA
padding = 2
}
作用域函数可以让我们在对对象进行频繁操作的时候,优化掉代码中对对象的引用,使代码看起来更加简洁明了
集合构造函数
大家在开发过程中或多或少的都会集合,而kotlin提供了标准库函数可以让大家方便快速的创建集合
//改造前
val listA = ArrayList<String>()
listA.add("one")
listA.add("two")
listA.add("three")
listA.add("four")
//改造后
val listB = mutableListOf("one", "two", "three", "four")
集合操作符
在项目开发过程中,我发现很多同事在对一些集合进行复杂的操作时,不懂得利用kotlin为我们提供的集合操作符。
其实许多对集合复杂的操作,用操作符一行就能搞定,然而还是有很多同事是自己写for循环操作的。比如下面这个,在listA中找到第一个长度大于3的字符串:
//改造前
var firstBigLengthElementA = ""
for (e in listA) {
if (e.length > 3) {
firstBigLengthElementA = e
break
}
}
//改造后
var firstBigLengthElementB = listA.first { it.length > 3 }
kotlin为方便我们对集合的操作,提供了很多集合操作符,下面列举几个常用并具有代表性的操作符:
| 操作符 | 作用 |
|---|---|
| find | 返回满足条件的成员 |
| first | 返回第一个满足条件的成员 |
| last | 返回最后一个满足条件的成员 |
| indexOf | 返回成员变量所在的索引 |
| take | 返回满足条件的子集合 |
| filter | 返回根据条件过滤出来的子集合 |
| sort | 根据排序条件,对当前集合进行排序 |
| all | 当集合中的所有成员都满足条件时,返回true |
| any | 如果集合中至少有一个成员满足条件,则返回true |
| sum | 返回集合中所有成员相加的和 |
除上述列举出来的扩展方法外,各个扩展方法又会有很多衍生的方法,比如 first 方法,还有衍生出来 firstOrNull 方法; take 方法还有衍生出来的 takeLast 方法等。
大家可以在自己kotlin工程中搜索打开_Collections.kt文件,查看更多集合操作符,也建议大家在对一个集合进行操作时,不妨先去这个文件中看下是否有适合自己的操作符进行使用。
延迟属性——lazy
延迟属性lazy是kotlin提供的委托属性的一种,关于kotlin委托属性会在后面的文章中详细讲解。
lazy() 是接受⼀个 lambda 并返回⼀个 Lazy 实例的函数,返回的实例可以作为属性的委托。
可以简单的理解为,利用委托属性+lazy方法,可以实现属性的延迟初始化:
data class Person(
val name: String,
val age: String,
val sex: String
) {
init {
println("person onCreate")
}
}
class Test {
//改造前
val personA: Person = Person("name", "age", "女")
//改造后
val personB: Person by lazy {
println("lazy create person")
Person("name", "age", "女")
}
init{
println("Test class onInit")
}
fun test() {
println("A's age is:${personA.age}")
println("B's age is:${personB.age}")
}
}
fun main() {
Test().test()
}
上述代码的运行结果如下: ![此处输入图片的描述][1] 可以看到personA是在类被创建的时候就被初始化了,而personB是在第一次被引用到时才被初始化。
这就是延迟属性的特点:对属性的初始化实行拉加载。
在开发过程中,我们应该尽量使用延迟属性来设计属性的初始化,可以避免属性在不必要的时机被初始化。
类型别名——typealias
typealias为现有类型提供替代名称。如果类型名称太⻓,你可以另外引⼊较短的名称,并使⽤新的名称 替代原类型名。
该关键字可以用于复杂的泛型类型,或者参数定义较多的函数类型,他可以使一行复杂的类型定义变得无比简短,让多次使用到该类型定义的代码变得简洁:
//改造前
val fileTableA: MutableMap<Long, MutableList<File>>? = null
fun getTestFileTableA(): MutableMap<Long, MutableList<File>>? = fileTableA
//改造后
typealias FileTable<K> = MutableMap<K, MutableList<File>>
val fileTableB: FileTable<Long>? = null
fun getTestFileTableB(): FileTable<Long>? = fileTableB
构造函数初始化属性
当类的属性的初始化是由构造函数传值进行,可使用更简洁的语法进行:
//改造前
class A(
name: String,
age: Int,
sex: String
) {
var name: String = name
var age: Int = age
var sex: String = sex
}
//改造后
class B(
var name: String,
var age: Int,
var sex: String
)
数据类——data class
数据类,顾名思义,是指只保存数据的类。 数据类相对于普通类来说不同的是,它会自动生成固定格式的以下函数:
- equals() / hashCode() 对;
- toString() 格式是 "User(name=John, age=42)" ;
- componentN() 函数 按声明顺序对应于所有属性;
- copy() 函数。
如果打印一个数据类对象默认的toString方法,那么会打印出一段指定格式的字符串,字符串中会打印出对象的类型与属性值。而打印一个普通类默认的toString方法,就不会有这么详尽的信息。
//改造后
data class A(
var name: String,
var age: Int,
var sex: String
)
//改造前
class B(
var name: String,
var age: Int,
var sex: String
)
fun main() {
println(A("A",1,"女"))
println(B("B",2,"女"))
}
上述代码的输出如下图 ![此处输入图片的描述][2]
对于数据类来说,我们最常使用的一个场景就是网络数据的request与response。这类数据往往没有继承关系,只是为了按照存储指定数据而使用。并且这类数据往往需要打印日志来查验网络回包的正确性。正好可以利用数据类的toString会自动生成指定格式的字符串这一特性。
命名参数
命名参数即给函数传参时,指定该参数的命名。
fun reformat(str: String,
normalizeCase: Boolean = true,
upperCaseFirstLetter: Boolean = true,
divideByCamelHumps: Boolean = false,
wordSeparator: Char = ' ') {
/*……*/
}
//改造前
reformat(str, true, true, false, '_')
//改造后
reformat(str,
normalizeCase = true,
upperCaseFirstLetter = true,
divideByCamelHumps = false,
wordSeparator = '_'
)
当一个函数有大量的参数或默认参数时,我们应当尽量使用命名参数的方式为方法传参,这样会极大的提高代码的可读性。
高级函数
高阶函数是将函数用作参数或返回值的函数。
所有函数类型都有一个圆括号括起来的参数类型列表以及一个返回类型:(A, B) -> C 表示接受类型分别为 A 与 B 两个参数并返回一个 C 类型值的函数类型。
而函数类型又是可以作为另一个函数的参数使用的。
在我们开发过程中,最常见也最适合使用函数类型的场景,是当我们调用一个有回调的函数,并且需要监听该函数的回调时。
为了将方法的回调逻辑与方法本身解耦,大家往往会选择将回调作为一个参数传入方法中。
而我发现很多同事选择将回调抽象成一个接口作为参数传给函数,其实在kotlin中,如果回调方法并不多,我们只需要传入一个方法类型即可:
//改造前
interface Listener {
fun onCallBack(rsp: Response)
}
fun onRequestA(listener: Listener) {
...
listener.onCallBack(rsp)
...
}
onRequestA(object : Listener {
override fun onCallBack(rsp: Response) {
print(rsp.code)
...
}
})
//改造后
fun onRequestB(callback: (rsp: RResponse) -> Unit) {
...
callback(rsp)
...
}
onRequestB {
print(rsp.code)
...
}
结语
后面还会更新文章对kotlin中的委托、协程等做进一步的描述。喜欢的童鞋记得点赞关注,以防走丢。
