Kotlin语法的高级特性异常强大,代码异常简洁,如果你在项目中能熟练使用各种kotlin高级特性后,你会发现,你之前这些年写的代码都是在浪费生命。
标准函数
kotlin的标准函数,指的是Standard.kt文件中定义的函数,包括let、also、with、run、apply函数。
-
let函数
let扩展函数的实际上是一个作用域函数,当你需要去定义一个变量在一个特定的作用域范围内,let函数的是一个不错的选择;let函数另一个作用就是可以避免写一些判断null的操作。
适用场景
场景一: 最常用的场景就是使用let函数处理需要针对一个可null的对象统一做判空处理。
//没有let函数,需要每次判空,代码不够优雅
data?.toString()
data?.toString()
data?.toString()
data?.let {
//在函数域中,保证data对象不为,不用多次去判空
it.toString()
it.toString()
it.toString()
}
场景二: 然后就是需要去明确一个变量所处特定的作用域范围内可以使用
data.let {
//在函数体内使用it替代该data对象使用
it.toString()
}
also函数
also函数使用的一般结构
object.also{
}
适用于let函数的任何场景,also函数和let很像,只是唯一的不同点就是let函数最后的返回值是最后一行的返回值而also函数的返回值是返回当前的这个对象。
user?.also {
it.age = 18
it.name = "小明"
}.age
with函数
with函数使用的一般结构
with(object){
//todo
}
它是将某对象作为函数的参数,在函数块内可以通过 this 指代该对象。
适用场景
需要设置某个对象多个属性到UI上时,需要不断调用对象,使用with函数能避免对象的重复书写。
data class User(var name: String, var age: Int)
with(user){
Log.i("TAG", "age=$age")
Log.i("TAG", "name=$name")
}
该age和name变量就是该with参数中的user对象的属性值。
run函数
run函数使用的一般结构
object.run{
}
适用场景
适用于let,with函数任何场景。因为run函数是let,with两个函数结合体,准确来说它弥补了let函数在函数体内必须使用it参数替代对象,在run函数中可以像with函数一样可以省略,直接访问实例的公有属性和方法,另一方面它弥补了with函数传入对象判空问题,在run函数中可以像let函数一样做判空处理。
user?.run {
Log.i("TAG", "age=$age")
Log.i("TAG", "name=$name")
}
apply函数
apply函数使用的一般结构
object.apply{
}
从结构上来看apply函数和run函数很像,唯一不同点就是它们各自返回的值不一样,run函数是以闭包形式返回最后一行代码的值,而apply可以任意调用该对象的任意方法,并返回该对象。
适用场景
整体作用功能和run函数很像,唯一不同点就是它返回的值是对象本身,而run函数是一个闭包形式返回,返回的是最后一行的值。
场景一:apply一般用于一个对象实例初始化的时候,需要对对象中的属性进行赋值。
data class User(var name: String, var age: Int)
ArrayList().apply {
add(User("小明", 18))
add(User("小王", 16))
}.run {
get(0)
}
创建User对象并添加入集合,添加完成后取出第一个对象。
场景二:多层级判空问题
data class Response(var code: Int, var data: Data)
data class Data(var icon: String, var title: String)
response?.apply {
//response不为空时可操作response
}.data?.apply {
//data不为空时可操作data
}.title?.apply {
//title不为空时可操作title
}
let,with,run,apply,also函数总结:
函数名 | 函数体内使用的对象 | 返回值 | 适用场景 |
---|---|---|---|
let | it指代当前对象 | 返回任意类型R | 适用于处理多处判空的场景 |
also | it指代当前对象 | 返回this | 适用于let函数的任何场景,一般可用于多个扩展函数链式调用 |
with | this指代当前对象或者省略 | 返回任意类型R | 适用于调用同一个类的多个方法、属性时,省去每次书写类名 |
run | this指代当前对象或者省略 | 返回任意类型R | 适用于let,with函数任何场景 |
apply | this指代当前对象或者省略 | 返回this | 适用于run函数任何场景,一般可用于多个扩展函数链式调用 |
takeIf,takeUnless函数
user.name.takeIf {
TextUtils.isEmpty(it)
}.let {
print(it)
}
user.name.takeUnless {
!TextUtils.isEmpty(it)
}.let {
print(it)
}
takeIf的闭包返回一个判断结果,如果为false时takeIf函数返回null;takeUnless与takeIf相反,为true时takeUnless函数返回null。
使用场景:只需要单个if分支语句的时候
优点:
可以配合其他作用域函数返回的结果,做出单向判断,保持链式调用
简化写法,逻辑清晰,减少代码量,代码更优雅
Kotlin委托
委托是一种设计模式,它的基本理念是:操作对象自己不会去处理某段逻辑,而是会把工作委托给另外一个辅助对象去处理。 比如调用A类的methodA方法,其实背后是B类的methodB去执行。
Kotlin将委托功能分为了两种:类委托和委托属性。
- 类委托:即一个类中定义的方法实际是调用另一个类的对象的方法来实现的。
interface User {
fun login()
}
class UserImpl(val name: String) : User{
override fun login() {
println(name)
}
}
class VipUser(user: User) : User by user
fun main() {
VipUser(UserImpl("1号用户")).login()
}
可以看到委托类并没有实现User接口,而是通过关键字by,将实现委托给了user。
- 属性委托:委托属性的核心思想是将一个属性(字段)的具体实现委托给另一个类去完成。
class MyClass {
var p by Delegate()
}
class Delegate {
var propValue: Any? = null
operator fun getValue(myClass: MyClass, prop: KProperty<*>): Any? {
return propValue
}
operator fun setValue(myClass: MyClass, prop: KProperty<*>, value: Any?) {
propValue = value
}
}
这里使用by关键字连接了左边的p属性和右边的Delegate实例,这种写法就代表着将p属性的具体实现委托给了Delegate类去完成。当调用p属性的时候会自动调用Delegate类的getValue()方法,当给p属性赋值的时候会自动调用Delegate类的setValue()方法。
by lazy并不是连在一起的关键字,只有by才是Kotlin中的关键字,lazy在这里只是一个高阶函数而已,返回的实例可以作为实现延迟属性的委托。
val lazyProp: String by lazy {
println("Hello,第一次调用才会执行我!")
"Hello"
}
// 打印lazyProp 3次,查看结果
fun main() {
println(lazyProp)
println(lazyProp)
println(lazyProp)
}
打印结果如下:
Hello,第一次调用才会执行我!
Hello
Hello
Hello
可以看到,只有第一次调用,才会执行lambda表达式中的逻辑,后面调用只会返回lambda表达式的最终值。
参考:
https://blog.csdn.net/xingyu19911016/article/details/126414296
https://mp.weixin.qq.com/s/QQy2zPcmtUL3SlRHMNWBEQ
扩展函数
扩展函数表示即使在不修改某个类的源码的情况下,我们仍然可以对某个类添加方法,进行扩展。例如我们对User类增加登录的功能,实现在类外面添加方法。
fun User.login() {
Log.i("TAG","去登录")
}
data class User(var name: String, var age: Int)
扩展了login方法,就可以使用user.login()。可以看到,扩展函数的主要写法就是在定义方法名的时候,通过Class.直接声明是在哪个类中。
集合操作符
Kotlin中可以通过集合操作符直接对集合进行操作,从而得到想要的结果。
map:对集合中的数据做改变,可以改变数据的类型。
filter:得到所有符合Lambda闭包中操作的数据。
find:得到符合Lambda闭包中操作的第一个数据。
findLast:得到符合Lambda闭包中操作的最后一个数据。
reduce:含有两个参数(集合中相邻的两个参数,用于遍历整个集合),对这两个参数进行操作,返回一个新参数,要求类型与集合中的参数类型相同。
协变与逆变
我们约定,在一个泛型类或者泛型接口的方法中,它的参数列表是接收数据的地方,就称为in位置,他的返回值是输出数据的地方,就称为out位置。
fun test(param: T):T {
return param
}
如上test方法中,传入泛型T的位置为in位置,返回类型T的位置为out位置。
泛型的协变:假如定义了一个MyClass
泛型的逆变:假如定义了一个MyClass
协程
见我的Kotlin协程使用
DSL(domain specific language)
即领域专用语言:专门解决某一特定问题的计算机语言,比如大家耳熟能详的 SQL 和正则表达式。
通用编程语言 vs DSL
通用编程语言(如 Java、Kotlin、Android等),往往提供了全面的库来帮助开发者开发完整的应用程序,而 DSL 只专注于某个领域,比如 SQL 仅支持数据库的相关处理,而正则表达式只用来检索和替换文本,我们无法用 SQL 或者正则表达式来开发一个完整的应用。
参考
Kotlin系列之let、with、run、apply、also函数的使用
Kotlin协程
Kotlin之美——DSL篇
一篇文章搞定kotlin