最近發(fā)生太多事,從吳某凡的瓜開始胁孙,到杭州市父女倆騎電動車起火唠倦,再到鄭州暴雨,還有2020年東京奧運(yùn)會涮较,記者們估計(jì)都忙瘋了吧稠鼻。。狂票。還有在線教育 K12 的毀滅性打擊枷餐,疫情又開始反復(fù),哎~ 這個(gè)暑假真的是太多事兒了,這是不是在教育我們毛肋,世事難料怨咪,要及時(shí)行樂?不管咋樣润匙,還是得腳踏實(shí)地诗眨,提升自我,只有這樣才有余力去幫助他人孕讳。
在上一篇中我們見識到了 Kotlin 語言所特有的幾種類——數(shù)據(jù)類匠楚、密閉類等,也熟悉了 Kotlin 中集合的常用運(yùn)算符厂财,以后再也不用擔(dān)心 Kotlin 集合的相關(guān)問題了芋簿。這是筆記系列的第三篇,漸入佳境了吧璃饱!
1. Kotlin 作用域函數(shù)
如果同學(xué)們已經(jīng)在項(xiàng)目中用過 Kotlin 語言与斤,那么一定見過 let 函數(shù)!因?yàn)槊慨?dāng) Kotlin 檢測到某個(gè)對象可能為空時(shí)荚恶,會自動幫我們修改為用 let 函數(shù)實(shí)現(xiàn):user.name?.let{ textView.text = it }
撩穿。這里的 let 函數(shù)就是 Kotlin 的作用域函數(shù)。除了 let谒撼,還有 run食寡、with、apply廓潜、also 等等作用域函數(shù)抵皱。
作用域函數(shù)是 Kotlin 內(nèi)置的,可對數(shù)據(jù)做一系列操作辩蛋、變換的函數(shù)呻畸,與集合操作符類似,作用域函數(shù)不僅僅可被集合對象調(diào)用堪澎,它們還可以被所有對象調(diào)用擂错。讓我們來看看它們的用法味滞。
let 和 run 函數(shù)類似樱蛤,都會返回函數(shù)內(nèi)閉包的結(jié)果,區(qū)別在于 let 有閉包參數(shù)剑鞍,而 run 沒有閉包參數(shù)昨凡。使用方法:let{ 閉包 }、run{ 閉包 }蚁署,有閉包參數(shù)意思是 let 在閉包中可以通過 it 拿到它自己本身便脊;而 run 就不行了,只能通過 this 關(guān)鍵字拿到它本身光戈∧奶担看 code 1 例子遂赠。
// code 1
data class Car(
val brand: String,
val price: Int
)
var car: Car? = null
fun funcExample() {
car = Car("紅旗", 199999)
// let 閉包里可用 it 訪問調(diào)用者,可返回閉包的執(zhí)行結(jié)果
val carBrand = car?.let { "car's brand is ${it.brand}" }
println(carBrand)
// run 閉包用 this 訪問調(diào)用者
val carPrice = car?.run { "car's price is ${this.price}" }
println(carPrice)
}
also和 apply 函數(shù)不會返回閉包里的結(jié)果晌杰,而上述的 let 和 run 是可以返回閉包結(jié)果的跷睦。also和 apply 函數(shù)的區(qū)別也是在于有無閉包參數(shù):also 有閉包參數(shù),apply 沒有閉包參數(shù)肋演。但是它們都會返回調(diào)用者對象抑诸,所以它們支持鏈?zhǔn)秸{(diào)用。
// code 2
// also 閉包里可用 it 訪問調(diào)用者爹殊,后面可鏈?zhǔn)秸{(diào)用
car?.also {
println("car 貼牌前 brand = ${it.brand}")
}?.brand = "比亞迪"
// apply 閉包用 this 訪問調(diào)用者蜕乡,后面也可鏈?zhǔn)秸{(diào)用
car?.apply {
println("car 貼牌后 brand = ${this.brand}")
}?.apply {
println("car's price = ${this.price}")
}
takeIf 和 takeUnless 這兩個(gè)作用域函數(shù)就用的相對較少了。takeIf 函數(shù)里的閉包返回的是 Boolean 類型梗夸,如果閉包條件滿足层玲,則返回調(diào)用者本身,如果不滿足绒瘦,則返回 null称簿。舉個(gè)栗子來說明吧。
// code 3
car?.takeIf { it.price > 1500000 }
?.also { println("車太貴啦惰帽!") } // 閉包為 true憨降,則不為空,執(zhí)行 also 函數(shù)閉包
?: run { println("價(jià)格還行该酗!") } // 閉包為 false授药,則返回空,執(zhí)行 run 函數(shù)閉包
takeUnless 跟 takeIf 是相反的關(guān)系,takeUnless 的閉包條件滿足則返回空,不滿足則返回調(diào)用者自己叹阔。
repeat 函數(shù)桶癣。調(diào)用方法:repeat( times ) { 閉包 }。將閉包的操作執(zhí)行 times 次瓤介。閉包里面的 it 是當(dāng)前執(zhí)行的循環(huán)次數(shù),從 0 開始計(jì)數(shù)。
// code 4
repeat(3) {
println("car' brand is ${car?.brand}, price is ${car?.price} 當(dāng)前執(zhí)行次數(shù)為:$it")
}
執(zhí)行結(jié)果:
car' brand is 比亞迪, price is 199999 當(dāng)前執(zhí)行索引為:0
car' brand is 比亞迪, price is 199999 當(dāng)前執(zhí)行索引為:1
car' brand is 比亞迪, price is 199999 當(dāng)前執(zhí)行索引為:2
with 函數(shù)趟庄。調(diào)用方法:with( T ){ 閉包 }。就是將對象 T 去執(zhí)行閉包里的操作伪很,通常在 Android 開發(fā)中戚啥,需要對一個(gè) TextView 賦值時(shí),就可以使用 with锉试,比較方便:
// code 5
with(textView) {
text = "測試"
textSize = 20F
setTextColor(ContextCompat.getColor(context, R.color.purple_200))
}
2. Kotlin 自定義操作符
學(xué)習(xí) Kotlin 一段時(shí)間后猫十,你會發(fā)現(xiàn) Kotlin 給了開發(fā)者很大的自我發(fā)展空間。比如:支持對類新增擴(kuò)展函數(shù),支持運(yùn)算符重載等拖云。所以贷笛,我們自己也可以自定義一些操作符,來方便開發(fā)宙项∽蛞洌看過 Kotlin 自帶的操作符實(shí)現(xiàn)的同學(xué)們會發(fā)現(xiàn),這些函數(shù)都是 inline 關(guān)鍵字修飾的杉允。我們先看下 inline 關(guān)鍵字邑贴。
inline 關(guān)鍵字,可以看做是一個(gè)是否 內(nèi)聯(lián) 的標(biāo)記叔磷。被修飾的函數(shù)會在編譯時(shí)拢驾,直接把函數(shù)體一起“拷貝”過去,就是將內(nèi)聯(lián)函數(shù)的代碼直接放在內(nèi)聯(lián)函數(shù)的位置上改基,這與一般函數(shù)不同繁疤,在調(diào)用一般函數(shù)的時(shí)候,是指令跳轉(zhuǎn)到被調(diào)用函數(shù)的入口地址秕狰,執(zhí)行完被調(diào)用函數(shù)后稠腊,指令再跳轉(zhuǎn)回原來跳進(jìn)來的地方繼續(xù)執(zhí)行后面的代碼;而由于內(nèi)聯(lián)函數(shù)是將函數(shù)的代碼直接放在了函數(shù)的位置上鸣哀,所以沒有指令跳轉(zhuǎn)架忌,指令按順序執(zhí)行。這樣做可以加快代碼的運(yùn)行速度我衬,但是會增加編譯時(shí)間以及編譯后的代碼量叹放。
inline 關(guān)鍵字適合修飾不太復(fù)雜的但會頻繁調(diào)用的函數(shù)。所以 Kotlin 自帶的操作符都是 inline 函數(shù)挠羔,我們?nèi)绻远x一個(gè)操作符井仰,也是需要修飾為 inline 函數(shù)。如下就是自定義了一個(gè) convert 操作符破加,功能類似集合中的 map 函數(shù)俱恶。
// 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 學(xué)習(xí)筆記(一)》 中介紹了下 Kotlin 反引號處理 Kotlin 關(guān)鍵字在 Java 代碼里沖突的問題。反引號還有一個(gè)作用范舀,就是在 Kotlin 代碼中將一個(gè)不合法的字符轉(zhuǎn)變?yōu)楹戏ㄗ址鲜恰Ee個(gè)栗子:
// code 7
object SmallTips {
// 反引號可將非法字符轉(zhuǎn)換為合法字符
fun `123`(){
println("函數(shù)名居然為`123`!")
}
fun ` `(){
println("函數(shù)名居然為` `尿背!")
}
fun ` `(){
println("函數(shù)名居然為` `端仰!")
}
}
// 調(diào)用也需將反引號加上
SmallTips.`123`()
SmallTips.` `()
SmallTips.` `()
不可思議吧捶惜!函數(shù)名本來不能為純數(shù)字或空格符田藐,但是加上反引號就可以了!神奇!那么這有啥用汽久?還記得 Kotlin 的 internal 訪問修飾符嗎鹤竭?它限定了被它修飾的函數(shù)只能在當(dāng)前模塊使用,而不能在其他模塊使用景醇。但是 Java 中是沒有這個(gè)修飾符的臀稚,而 Kotlin 和 Java 又必須完全兼容,所以 Java 也不得不支持這一特性三痰。
那么問題來了吧寺,通過反編譯查看 Kotlin 中 internal 修飾的函數(shù),在生成的 Java 代碼里被編譯成了 public 修飾的函數(shù)(笑Cry.gif)散劫。為了讓 Java 不能訪問 Kotlin 中的函數(shù)稚机,可以在 Kotlin 中將這些函數(shù)的命名改為不合法的形式,然后用反引號包起來获搏,這么做之后赖条,Java 代碼是不能調(diào)用這些方法的,而 Kotlin 可以調(diào)用常熙,從而可以實(shí)現(xiàn)在 Java 中屏蔽某些 Kotlin 函數(shù)的效果纬乍。最后,這種反引號的用法不推薦使用裸卫!了解即可仿贬!
4. Kotlin 對象比較
在 Java 中,要比較兩個(gè)對象是否相等墓贿,通常用的是 == 或 equals 方法诅蝶。Java 中的 == 運(yùn)算符比較的是兩個(gè)對象本身的值,即兩個(gè)對象在內(nèi)存中的首地址募壕。如果是兩個(gè)字符串调炬,就是比較的這兩個(gè)字符串存儲的地址是否是同一個(gè)。
Java 中舱馅,對象的首地址是它在內(nèi)存中存放的起始地址缰泡,它后面的地址是用來存放它所包含的各個(gè)屬性的地址,所以內(nèi)存中會用多個(gè)內(nèi)存塊來存放對象的各個(gè)屬性值代嗤,而通過這個(gè)首地址就可以找到該對象棘钞,進(jìn)而可以找到該對象的各個(gè)屬性。
Java 中的 equals 方法比較的是兩個(gè)對象中各個(gè)屬性值的是否相同干毅。如果是兩個(gè)字符串宜猜,就是比較的兩字符串所包含的內(nèi)容是否相同。
在 Kotlin 語言中硝逢,判斷兩個(gè)對象是否相等用的是 == 和 ===姨拥。沒錯(cuò)绅喉,兩個(gè)等號和三個(gè)等號。Kotlin 的 == 相當(dāng)于 Java 中的 equals 方法叫乌;而 === 相當(dāng)于 Java 中的 == 運(yùn)算符柴罐,記住即可。栗子也有憨奸,看下方:
// code 8
val str1 = java.lang.String("我發(fā)")
val str2 = java.lang.String("我發(fā)")
println("str1 == str2 結(jié)果為 ${str1 == str2}") // 輸出:str1 == str2 結(jié)果為 true
println("str1 === str2 結(jié)果為 ${str1 === str2}") // 輸出:str1 === str2 結(jié)果為 false
因?yàn)樵?Kotlin 的 String 構(gòu)造方法中革屠,不能直接傳入一個(gè)字符串,所以這里用的是 Java 中的 String 類進(jìn)行的初始化排宰。也可以用 Kotlin 的 String 另外兩種初始化方法:1)val str1 = StringBuilder("我發(fā)").toString()似芝;2)val str1 = String("我發(fā)".toByteArray())。
5. Kotlin 的常量變量
根據(jù)筆記一中的內(nèi)容板甘,我們知道国觉,Kotlin 有兩種變量,一種是用 val 關(guān)鍵字修飾的不可變的變量虾啦;另一種是用 var 關(guān)鍵字修飾的可變的變量麻诀。如何在類中對這兩種變量進(jìn)行初始化呢?val 因?yàn)槭遣豢勺儼磷恚灾荒苤貙懽兞康?getter 方法蝇闭,var 則可以重寫 getter 和 setter 方法,當(dāng)然類會自動幫我們生成 getter 和 setter 方法硬毕。
// code 9
class Person { // 此類無實(shí)際意義呻引,為了舉個(gè)栗子而已
var age: Int = 0 // 可變變量我們可以重寫 getter 和 setter
get() {
return field.plus(10)
}
set(value) {
field = value - 1
}
val name: String = "" // 不可變變量我們只能重寫 getter 方法
get() {
return field + "haha"
}
val height: Float // height 是用 val 修飾的,但 height 并不是一個(gè)常量
get() {
return (age * 2F + 10)
}
}
在重寫 getter 和 setter 方法時(shí)吐咳,可以通過 field 拿到該屬性的值逻悠。val 和 var 最本質(zhì)的區(qū)別就是,val 沒有 setter 方法韭脊。val 并不是常量童谒,而是說,我們不能再次對 val 變量進(jìn)行賦值操作沪羔!為啥 val 修飾的并不是常量饥伊?可以看一下 code 9 中的 height 變量,當(dāng) age 變化時(shí)蔫饰,height 也會變化琅豆,它并不是一個(gè)常量。
如果要聲明一個(gè)常量篓吁,則要用到 const 關(guān)鍵字茫因。它有兩個(gè)注意點(diǎn):
1)const 只能修飾 object 的屬性,或 top-level 變量杖剪。
2)const 變量的值必須在編譯期間就確定下來冻押,所以類型只能是 String 或基本數(shù)據(jù)類型驰贷。
啥意思呢?我理解的就是翼雀,Kotlin 中用 const 修飾的常量類似于 Java 中的一個(gè)不可變的靜態(tài)變量。它聲明的地方只有三種1. object 類的內(nèi)部孩擂,object 修飾的都是靜態(tài)類狼渊;2. top-level 位置,也就是在一個(gè)類的外部進(jìn)行聲明类垦;3. companion object 內(nèi)部狈邑,也就是用于聲明靜態(tài)變量的位置。
// code 10
object ValAndVarExample {
const val t2 = "heiheihei"
}
const val t1 = "hahaha" // top-level蚤认,類外
class Person {
companion object{
const val t3 = "hehehe"
}
}
6. Kotlin 的 inline米苹、crossinline、noinline 關(guān)鍵字的特殊使用
在前面的第2節(jié) Kotlin 的自定義操作符中砰琢,已經(jīng)說明了 inline 關(guān)鍵字的基本用法蘸嘶,知道了內(nèi)聯(lián)函數(shù)可以通過直接將代碼拷貝到調(diào)用的地方從而加快程序執(zhí)行速度的特性。除了 inline 關(guān)鍵字外陪汽,還有 crossinline 和 noinline 兩個(gè)關(guān)鍵字训唱,來看看它們還有什么特殊的用法。
在講之前挚冤,還是需要明白一些前提知識况增。inline 關(guān)鍵字既會影響函數(shù)對象本身,也會影響傳入的 Lambda 閉包參數(shù)训挡,兩者都會被內(nèi)聯(lián)到調(diào)用點(diǎn)澳骤。
編譯預(yù)處理器會對內(nèi)聯(lián)函數(shù)進(jìn)行擴(kuò)展,省去了參數(shù)壓棧澜薄、生成匯編語言的 CALL 調(diào)用为肮、返回參數(shù)、執(zhí)行 return 等過程肤京,從而提高運(yùn)行速度弥锄。優(yōu)點(diǎn)是,在函數(shù)被內(nèi)聯(lián)后編譯器可以通過上下文相關(guān)的優(yōu)化技術(shù)對結(jié)果代碼執(zhí)行更深入的優(yōu)化蟆沫;但會使得編譯后的代碼體積變大籽暇,只是省去了函數(shù)調(diào)用的開銷。所以 inline 適合用于較簡單的頻繁調(diào)用的函數(shù)饭庞。
6.1. 被 inline 修飾的函數(shù)中的 Lambda 表達(dá)式戒悠,可以中斷外部函數(shù)的調(diào)用。
啥意思舟山?沒關(guān)系绸狐,大家都是一臉懵卤恳。得結(jié)合例子說一下子:
// code 11
fun main(args: Array<String>) {
test1 {
println("我要好好學(xué) Kotlin")
return
}
println("我要好好學(xué)習(xí) Android")
}
inline fun test1(lambda: () -> Unit) {
lambda.invoke()
}
// 輸出:我要好好學(xué) Kotlin
test1 函數(shù)被 inline 修飾,它有個(gè) Lambda 閉包寒矿,在該閉包中有個(gè) return 返回函數(shù)突琳,這個(gè)函數(shù)可以中斷外部的 main 函數(shù),所以只會輸出 “我要好好學(xué) Kotlin”符相。
通常情況下拆融,Kotlin 中函數(shù)內(nèi)部 Lambda 閉包是不能中斷外部函數(shù)的執(zhí)行的,可以嘗試下將 code 11 中 test1 修飾的 inline 去掉啊终,此時(shí)編譯器就會提示 return 只能寫成 return@test1镜豹,即只能返回 test1 函數(shù),并不能返回 main 函數(shù)蓝牲。
6.2. crossinline 關(guān)鍵字不允許被 inline 修飾的函數(shù)中的 Lambda 表達(dá)式中斷外部函數(shù)的執(zhí)行趟脂。
意思就是,在 code 11 中例衍,如果 Lambda 表達(dá)式的 return 只是想中斷該閉包的執(zhí)行昔期,而不想中斷外部 main 函數(shù)的執(zhí)行,該咋辦佛玄?有人會說镇眷,那我不用 inline 不就可以了?但這里又需要用 inline 呢翎嫡?那就可以使用 crossinline 去修飾這個(gè) Lambda 閉包欠动,編譯器就不會去對這個(gè) Lambda 表達(dá)式做內(nèi)聯(lián)操作。
// code 12
fun main(args: Array<String>) {
test1 {
println("我要好好學(xué) Kotlin")
return@test1
println("我不想學(xué)習(xí)了~")
}
println("我要好好學(xué)習(xí) Android")
}
inline fun test1(crossinline lambda: () -> Unit) {
lambda.invoke()
}
// 輸出:
//我要好好學(xué) Kotlin
//我要好好學(xué)習(xí) Android
6.3. noinline 關(guān)鍵字不允許被 inline 修飾的函數(shù)中的 Lambda 表達(dá)式被內(nèi)聯(lián)處理惑申。
首先具伍,noinline 關(guān)鍵字是作用于 Lambda 閉包的;其次圈驼,它是用于在修飾了 inline 關(guān)鍵字的函數(shù)中人芽,剔除 inline 關(guān)鍵字對 Lambda 閉包的影響,讓它就作為一個(gè)普通的 Lambda 閉包绩脆。說明不夠萤厅,代碼來湊!
// code 13
inline fun test2(lambda0: () -> Unit, noinline lambda1: () -> Unit): () -> Unit {
lambda0.invoke()
lambda1.invoke()
return lambda1
}
test2 函數(shù)被 inline 修飾靴迫,有兩個(gè) Lambda 閉包作為參數(shù)惕味,而且它的返回值也是一個(gè) Lambda 閉包。如果 lambda1 沒有 noinline 關(guān)鍵字修飾玉锌,那么它就會跟 lambda0 一樣名挥,將函數(shù)體直接拷貝到調(diào)用的地方,這種情況下主守,lambda1 就不能作為閉包返回了禀倔,所以去掉 noinline 之后榄融,code 13 代碼會報(bào)錯(cuò)。所以救湖,這里如果要將 test2 用 inline 修飾愧杯,同時(shí),又想返回一個(gè)閉包的話鞋既,就可以用 noinline 關(guān)鍵字去除 inline 對閉包的影響力九。
上面說的都是關(guān)于 inline 關(guān)鍵字的進(jìn)階用法,通常情況下不會用到涛救,作為知識儲備即可畏邢。
這篇筆記就到此為止了业扒,更多 Kotlin 學(xué)習(xí)筆記可以查看
上一篇:Kotlin 學(xué)習(xí)筆記(二)
下一篇:(Writing...)
還沒看夠检吆?歡迎來我的公眾號轉(zhuǎn)轉(zhuǎn)~
參考文獻(xiàn)
- 張濤;極客時(shí)間 Kotlin 系列課程
- 深山里的小白羊程储;《內(nèi)聯(lián)函數(shù)》 https://blog.csdn.ne/qq_33757398蹭沛。
- 韋邦杠;《java中equals以及==的用法(簡單介紹)》 https://www.cnblogs.com/weibanggang/p/9457757.html章鲤。
- One_Month摊灭;《Kotlin中的noinline》 https://blog.csdn.net/One_Month/article/details/108980646。
- DONGYUXIA败徊;《Kotlin基礎(chǔ)之內(nèi)聯(lián)函數(shù)》 http://blog.chinaunix.net/uid-31478279-id-5782198.html帚呼。
ps. 贈人玫瑰,手留余香皱蹦。歡迎轉(zhuǎn)發(fā)分享加關(guān)注煤杀,你的認(rèn)可是我繼續(xù)創(chuàng)作的精神源泉。