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<User>().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指代当前对象 | 闭包形式返回 | 适用于处理多处判空的场景 |
| also | it指代当前对象 | 返回this | 适用于let函数的任何场景,一般可用于多个扩展函数链式调用 |
| with | this指代当前对象或者省略 | 闭包形式返回 | 适用于调用同一个类的多个方法、属性时,省去每次书写类名 |
| run | this指代当前对象或者省略 | 闭包形式返回 | 适用于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分支语句的时候
优点:
可以配合其他作用域函数返回的结果,做出单向判断,保持链式调用
简化写法,逻辑清晰,减少代码量,代码更优雅
委托
委托是软件设计的一种模式,当无法或不想访问某个对象或访问某个对象存在困难时,可以交给委托类来处理。
- 类委托:即一个类中定义的方法实际是调用另一个类的对象的方法来实现的。
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。
- 属性委托:指的是一个类的某个属性值不是在类中直接定义,而是将其委托给一个代理类,从而实现对该类属性的统一管理。
扩展函数
扩展函数表示即使在不修改某个类的源码的情况下,我们仍然可以对某个类添加方法,进行扩展。例如我们对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 <T>test(param: T):T {
return param
}
如上test方法中,传入泛型T的位置为in位置,返回类型T的位置为out位置。
泛型的协变:假如定义了一个MyClass<T>的泛型类,其中A是B的子类型,同时MyClass<A>又是MyClass<B>的子类型,我们就可以称MyClass在T这个泛型是协变的。如果泛型都是只读的(泛型加上out关键字),就能实现MyClass<A>是MyClass<B>的子类型
泛型的逆变:假如定义了一个MyClass<T>的泛型类,其中A是B的子类型,同时MyClass<B>又是MyClass<A>的子类型,我们就可以称MyClass在T这个泛型是逆变的。如果泛型都是只写的(泛型加上in关键字),就能实现MyClass<B>是MyClass<A>的子类型
高阶函数
如果一个函数接收另一个函数作为参数,或者返回类型是一个函数,那么这个函数我们就称之为高阶函数。
简单用例:
class Gaojie {
fun main() {
calculate(10, 5, ::add)
}
}
/**
* 加法
*/
fun add(a: Int, b: Int): Int {
return a+b
}
/**
* 减法
*/
fun minus(a: Int, b: Int): Int {
return a-b
}
/**
* 运算
*
* operate:(Int, Int)->Int 表示定义的一个函数,将其作为calculate参数的形参,实际传的时候是传入add或minus函数
*/
fun calculate(a:Int, b:Int, operate:(Int, Int)->Int) {
var result = operate(a,b)
print("操作结果为 $result")
}
首先定义了一个高阶函数,他传入三个参数,两个Int型的值和一个函数类型的值,在方法内部调用“函数类型的值”,因为它本身是函数,所以可以直接调用,并且将前两个Int型作为形参传了进去。接下来我们定义了两个函数add和minus,这两个函数实现他们本身的逻辑,最后在main函数里面调用了此高阶函数,其中::add和::minus是固定写法,表示函数的引用。
协程
协程通过将复杂性放入库来简化异步编程。程序的逻辑可以在协程中顺序地表达,而底层库会为我们解决其异步性。该库可以将用户代码的相关部分包装为回调、订阅相关事件、在不同线程(甚至不同机器)上调度执行,而代码则保持如同顺序执行一样简单。
DSL(domain specific language)
即领域专用语言:专门解决某一特定问题的计算机语言,比如大家耳熟能详的 SQL 和正则表达式。
通用编程语言 vs DSL
通用编程语言(如 Java、Kotlin、Android等),往往提供了全面的库来帮助开发者开发完整的应用程序,而 DSL 只专注于某个领域,比如 SQL 仅支持数据库的相关处理,而正则表达式只用来检索和替换文本,我们无法用 SQL 或者正则表达式来开发一个完整的应用。
参考
Kotlin系列之let、with、run、apply、also函数的使用
Kotlin协程
Kotlin之美——DSL篇
一篇文章搞定kotlin












网友评论