在上一篇中我们见识到了 Kotlin 语言所特有的几种类——数据类、密闭类等,也熟悉了 Kotlin 中集合的常用运算符,以后再也不用担心 Kotlin 集合的相关问题了。这是笔记系列的第四篇,渐入佳境了吧!
1. Kotlin 作用域函数
如果同学们已经在项目中用过 Kotlin 语言,那么一定见过 let 函数!因为每当 Kotlin 检测到某个对象可能为空时,会自动帮我们修改为用 let 函数实现:user.name?.let{ textView.text = it }
。这里的 let 函数就是 Kotlin 的作用域函数。除了 let,还有 run、with、apply、also 等等作用域函数。
作用域函数是 Kotlin 内置的,可对数据做一系列操作、变换的函数,与集合操作符类似,作用域函数不仅仅可被集合对象调用,它们还可以被所有对象调用。让我们来看看它们的用法。
let 和 run 函数类似,都会返回函数内闭包的结果,区别在于 let 有闭包参数,而 run 没有闭包参数。使用方法:let{ 闭包 }、run{ 闭包 },有闭包参数意思是 let 在闭包中可以通过 it 拿到它自己本身;而 run 就不行了,只能通过 this 关键字拿到它本身。看 code 1 例子。
// code 1 data class Car( val brand: String, val price: Int ) var car: Car? = null fun funcExample() { car = Car("红旗", 199999) // let 闭包里可用 it 访问调用者,可返回闭包的执行结果 val carBrand = car?.let { "car's brand is ${it.brand}" } println(carBrand) // run 闭包用 this 访问调用者 val carPrice = car?.run { "car's price is ${this.price}" } println(carPrice) }
also和 apply 函数不会返回闭包里的结果,而上述的 let 和 run 是可以返回闭包结果的。also和 apply 函数的区别也是在于有无闭包参数:also 有闭包参数,apply 没有闭包参数。但是它们都会返回调用者对象,所以它们支持链式调用。
// code 2 // also 闭包里可用 it 访问调用者,后面可链式调用 car?.also { println("car 贴牌前 brand = ${it.brand}") }?.brand = "比亚迪" // apply 闭包用 this 访问调用者,后面也可链式调用 car?.apply { println("car 贴牌后 brand = ${this.brand}") }?.apply { println("car's price = ${this.price}") }
takeIf 和 takeUnless 这两个作用域函数就用的相对较少了。takeIf 函数里的闭包返回的是 Boolean 类型,如果闭包条件满足,则返回调用者本身,如果不满足,则返回 null。举个栗子来说明吧。
// code 3 car?.takeIf { it.price > 1500000 } ?.also { println("车太贵啦!") } // 闭包为 true,则不为空,执行 also 函数闭包 ?: run { println("价格还行!") } // 闭包为 false,则返回空,执行 run 函数闭包
takeUnless 跟 takeIf 是相反的关系,takeUnless 的闭包条件满足则返回空,不满足则返回调用者自己。
repeat 函数。调用方法:repeat( times ) { 闭包 }。将闭包的操作执行 times 次。闭包里面的 it 是当前执行的循环次数,从 0 开始计数。
// code 4 repeat(3) { println("car' brand is ${car?.brand}, price is ${car?.price} 当前执行次数为:$it") } 执行结果: car' brand is 比亚迪, price is 199999 当前执行索引为:0 car' brand is 比亚迪, price is 199999 当前执行索引为:1 car' brand is 比亚迪, price is 199999 当前执行索引为:2
with 函数。调用方法:with( T ){ 闭包 }。就是将对象 T 去执行闭包里的操作,通常在 Android 开发中,需要对一个 TextView 赋值时,就可以使用 with,比较方便:
// code 5 with(textView) { text = "测试" textSize = 20F setTextColor(ContextCompat.getColor(context, R.color.purple_200)) }
2. Kotlin 自定义操作符
学习 Kotlin 一段时间后,你会发现 Kotlin 给了开发者很大的自我发展空间。比如:支持对类新增扩展函数,支持运算符重载等。所以,我们自己也可以自定义一些操作符,来方便开发。看过 Kotlin 自带的操作符实现的同学们会发现,这些函数都是 inline 关键字修饰的。我们先看下 inline 关键字。
inline 关键字,可以看做是一个是否 内联 的标记。被修饰的函数会在编译时,直接把函数体一起“拷贝”过去,就是将内联函数的代码直接放在内联函数的位置上,这与一般函数不同,在调用一般函数的时候,是指令跳转到被调用函数的入口地址,执行完被调用函数后,指令再跳转回原来跳进来的地方继续执行后面的代码;而由于内联函数是将函数的代码直接放在了函数的位置上,所以没有指令跳转,指令按顺序执行。这样做可以加快代码的运行速度,但是会增加编译时间以及编译后的代码量。
inline 关键字适合修饰不太复杂的但会频繁调用的函数。所以 Kotlin 自带的操作符都是 inline 函数,我们如果要自定义一个操作符,也是需要修饰为 inline 函数。如下就是自定义了一个 convert 操作符,功能类似集合中的 map 函数。
// code 6 inline fun <T, E> Iterable<T>.convert(action: (T) -> E): Iterable<E> { val list: MutableList<E> = mutableListOf() for (item in this) list.add(action(item)) return list }
3. Kotlin 中反引号 ` 的用法
在前面的《Kotlin 学习笔记(一)》 中介绍了下 Kotlin 反引号处理 Kotlin 关键字在 Java 代码里冲突的问题。反引号还有一个作用,就是在 Kotlin 代码中将一个不合法的字符转变为合法字符。举个栗子:
// code 7 object SmallTips { // 反引号可将非法字符转换为合法字符 fun `123`(){ println("函数名居然为`123`!") } fun ` `(){ println("函数名居然为` `!") } fun ` `(){ println("函数名居然为` `!") } } // 调用也需将反引号加上 SmallTips.`123`() SmallTips.` `() SmallTips.` `()
不可思议吧!函数名本来不能为纯数字或空格符,但是加上反引号就可以了!神奇!那么这有啥用?还记得 Kotlin 的 internal 访问修饰符吗?它限定了被它修饰的函数只能在当前模块使用,而不能在其他模块使用。但是 Java 中是没有这个修饰符的,而 Kotlin 和 Java 又必须完全兼容,所以 Java 也不得不支持这一特性。
那么问题来了,通过反编译查看 Kotlin 中 internal 修饰的函数,在生成的 Java 代码里被编译成了 public 修饰的函数(笑Cry.gif)。为了让 Java 不能访问 Kotlin 中的函数,可以在 Kotlin 中将这些函数的命名改为不合法的形式,然后用反引号包起来,这么做之后,Java 代码是不能调用这些方法的,而 Kotlin 可以调用,从而可以实现在 Java 中屏蔽某些 Kotlin 函数的效果。最后,这种反引号的用法不推荐使用!了解即可!