kotlin 上手很簡單瓢捉,因為可以完美支持 java 捂刺,和 java 比較像的緣故到踏,我們熟悉下 kotlin 的語法鲫竞,1-2天就能寫出 java 語法式的 kotlin 代碼了,但是我們絕對不能只不如此固阁,kotlin 本身的高級特性代表著語言的發(fā)展趨勢壤躲,本身也是很簡單,高效的备燃,我們必須真正熟悉 kotlin 自身的寫法碉克,不要抗拒,擁抱 kotlin并齐,零碎東西不少漏麦,但是我們總結(jié)一下客税,平時多用用,也就熟悉了
本文包含以下內(nèi)容:
-
委托
- Lazy / lateinit 延遲加載器
- Delegates 屬性觀察者
- map / MutableMap 委托構(gòu)造函數(shù)
- 智能轉(zhuǎn)換類型
- this 表達式
- typealias 類型別名
- let / apply / run / with 這幾個 kotlin 特有的擴展函數(shù)
- in / out
- return / break / continue
Lazy / lateinit
kotlin 對于 null 有著嚴格的使用限制撕贞,處處可見 更耻?不是,非 null 判斷一直是代碼的痛點捏膨,kotlin 只是把這種 痛點變得對 coder 來說更有好了秧均,但是凡事總有一利一弊不是
kotlin 為了準確定義是不是 null ,要求我們在定義全局變量号涯,時必須顯示的賦值
傳統(tǒng) java 中我們這樣定義全局變量
public class Car {
public String name ;
}
kotlin 中我們必須賦值目胡,否則會報錯
這里 kotlin 提供了2個選擇,Lazy / lateinit 链快,都是延遲加載誉己,但是有區(qū)別:
Lazy 只能修飾 val 不可變參數(shù),等同于 java 的 final 域蜗,其次 Lazy 后跟一個 {} 復(fù)制表達式巨双,本質(zhì)是在一個工廠函數(shù),只有在第一次調(diào)用時生效(既首席創(chuàng)建對象時)霉祸,常用于單例模式
val lazyValue: String by lazy {
// println 是對象創(chuàng)建時初始化操作
println("computed!")
// Hello 是返回的對象
"Hello"
}
//調(diào)用兩次
println(lazyValue)
println(lazyValue)
// 第一次
computed!
Hello
// 第二次
Hello
lazy 方法本質(zhì)是個 lamber 表達式筑累,在 lazy() 中我們可以傳入線程類型參數(shù):
- LazyThreadSafetyMode.SYNCHRONIZED: 初始化屬性時會有雙重鎖檢查,保證該值只在一個線程中計算脉执,并且所有線程會得到相同的值
- LazyThreadSafetyMode.PUBLICATION: 多個線程會同時執(zhí)行,初始化屬性的函數(shù)會被多次調(diào)用半夷,但是只有第一個返回的值被當做委托屬性的值
- LazyThreadSafetyMode.NONE: 沒有雙重鎖檢查迅细,不應(yīng)該用在多線程下
lateinit 我們可以在可以修飾任意參數(shù),可以是 var 茵典、 val 的,我們在聲明成員變量時可以不指定具體數(shù)值统阿,但是 lateinit 修飾的參數(shù)必須在合適的地方初始化,否則編譯不會通過
我們看個例子扶平,下面我們聲明 lateinit 的參數(shù),但是不初始化直接使用
open class News(var room: String) {
lateinit var name: String
lateinit var book: Book
fun speak() {
println(name)
}
fun price() {
println(book)
print(room)
}
}
使用
btn_name.setOnClickListener(View.OnClickListener {
val news = News("book")
news.speak()
news.price()
})
編譯會報 UninitializedPropertyAccessException 異常结澄,未初始化的參數(shù)不可達
這就是 Lazy 和 lateinit 的區(qū)別岸夯,lateinit 我們在使用前必須初始化才行们妥,而 Lazy 在我們首席使用的時候才會創(chuàng)建對象猜扮,很像 java 中的懶漢式,餓漢式监婶。上面的代碼我們即使對 lateinit 的參數(shù)加了 !null 的判斷也沒用
如果在我們的代碼場景中會有像 java 那樣旅赢,成員變量依靠外接傳遞,那么 kotlin 也提供了相關(guān)寫法惑惶,核心就是拋棄 kotlin 關(guān)于 null 的操作鲜漩,完全還原 java 的環(huán)境,還是上面的代碼
open class News(var room: String) {
var name: String? = null
var book: Book? = null
fun speak() {
if (name != null) {
println(name!!)
}
}
fun price() {
if (book != null && room != null) {
println(book!!)
print(room!!)
}
}
}
我們就不用 lateinit 來修飾啦集惋,在使用這個參數(shù)時添加 !! 后綴表示按照 java 語法進行孕似,注意我們現(xiàn)在得進行 !null 判斷啦,要不會報錯的哦~
Delegates 屬性觀察者
Delegates 我是愿意稱為屬性觀察者的刮刑,Delegates 下面包含一些列函數(shù)喉祭,這是 kotlin 獨有的特性,允許我們在屬性賦值時添加觀察者雷绢,攔截器操作
我們常用的是 observable / vetoable 這2個函數(shù)
- observable 可以觀察參數(shù)的變化
- vetoable 相當于參數(shù)攔截器泛烙,可以攔截不符合條件的復(fù)制操作
- 但是在這2個函數(shù)內(nèi)我們都不能主動的修改參數(shù)值,代碼檢查會提示我們
我們來看看代碼:
open class News(var room: String) {
var title: String by Delegates.observable("title_default") { property, oldValue, newValue ->
Log.d("AAA", "屬性變化:屬性名:$property 舊值:$oldValue 新值:$newValue")
}
var price: Int by Delegates.vetoable(100, { property, oldValue, newValue ->
if (newValue > 100) {
Log.d("AAA", "屬性變化:屬性名:$property 舊值:$newValue > 100 不符合需求不能更改數(shù)據(jù)")
return@vetoable false
}
return@vetoable true
})
}
Delegates 下屬函數(shù)會提供給我們3個參數(shù)翘紊,property(參數(shù)名) / oldValue(舊值) / newValue(新值)蔽氨,接收2個參數(shù),前一個是默認值帆疟,賦值時注意數(shù)據(jù)類型鹉究;后一個接受一個對象函數(shù)用來包裹我們的代碼
observable 函數(shù)沒有返回值,vetoable 函數(shù)有返回值踪宠,true 表示允許參數(shù)修改自赔,false 反之不允許,數(shù)據(jù)不會變更柳琢。這里注意我們要顯式的使用 return@vetoable 退出函數(shù)绍妨,否則會出現(xiàn)代碼穿透的問題
下面來試一下,我們給 title 和 price 賦值看看:
val news = News("book")
news.title = "AAA"
news.price = 200
Log.d("AA", "重新對 news 賦值后柬脸,news 的值:${news.price}")
屬性變化:屬性名:property title (Kotlin reflection is not available) 舊值:title_default 新值:AAA
屬性變化:屬性名:property price (Kotlin reflection is not available) 舊值:200 > 100 不符合需求不能更改數(shù)據(jù)
重新對 news 賦值后他去,news 的值:100
map 構(gòu)造函數(shù)委托
kotlin 的這個 map 委托是用與構(gòu)造函數(shù)的,生成數(shù)據(jù)的倒堕,用于 json 解析灾测,我覺得用這個 map 做 json 解析不是好,不如 gson
map:
// map 用在類聲明處涩馆,傳參用
class BookData(map: Map<String, Any>) {
val name: String by map
val price: Int by map
fun speak() {
Log.d("AAA", "BookData: name = $name , price = $price")
}
}
// 使用:
var bookData = BookData(mapOf("name" to "", "price" to 88))
bookData.speak()
map 只能修飾 val 不可變參數(shù)允坚,那么相應(yīng)的就有 MutableMap 蛾号,注意 Mutable 可變早 kotlin 已經(jīng)出現(xiàn)在好幾個地方了
MutableMap :
class BookData(map: MutableMap<String, Any>) {
var name: String by map
var price: Int by map
fun speak() {
Log.d("AAA", "BookData: name = $name , price = $price")
}
}
MutableMap 可以操作 var 可變參數(shù)了鲜结,和 map 就是這點差距
需要注意的是,使用 map 賦值生成數(shù)據(jù)對象時精刷,比如傳入所有的屬性值,沒有值的也要給埂软,要不會拋出 error勘畔,參數(shù)類型給錯了也會報錯
錯誤賦值丽惶,缺少一個屬性值:
var bookData = BookData(mapOf("name" to "android"))
bookData.speak()
前面說到有人推薦使用 map 來進行 json 操作钾唬,這里我推行各位必須找資料看明白再決定是不是使用 map 這個特性
智能轉(zhuǎn)換類型
kotlin 是弱類型語言,通過 var 大家都了解吧奕巍,這點和 java 不同, java 這種強類型設(shè)計早早就被時代慢慢淘汰了伍绳,到現(xiàn)在 var / val 這種弱類型設(shè)計已經(jīng)是行業(yè)準則了,java 在 jdk 10 時也開始支持 var 了
var 的好處是語言可以自定判斷數(shù)據(jù)類型效床,從而進行無痕式的類型轉(zhuǎn)換,這點對于我們來說體驗是很 nice 的憋沿,代碼少了沪猴,也不會打算思路寫討厭重復(fù)的代碼了采章,邏輯直接一氣呵成悯舟,連貫舒服我婆覺得是語言進行的特點
kotlin 強制類型轉(zhuǎn)換
var kkk:Any = "123"
var mmm:String = kkk as String
類型判斷
var kkk:Any = "123"
var mmm:String = kkk as String
mmm is String
智能自動轉(zhuǎn)換類型
var kkk:Any = "123"
// 不用我們自己手動寫轉(zhuǎn)換了吧抵怎,這是因為我們已經(jīng)做了類型判斷了岭参,所以編譯器認為類型安全默認給我們轉(zhuǎn)了
if (kkk is String) {
kkk.length
}
當然智能轉(zhuǎn)換不是萬能的,機器畢竟不是人不是姿染,適用以下規(guī)則:
- val 局部變量——總是可以秒际,局部委托屬性除外
- val 屬性——如果屬性是 private 或 internal程癌,或者該檢查在聲明屬性的同一模塊中執(zhí)行。智能轉(zhuǎn)換不適用于 open 的屬性或者具有自定義 getter 的屬性
- var 局部變量——如果變量在檢查和使用之間沒有修改进萄、沒有在會修改它的 lambda 中捕獲锐峭、并且不是局部委托屬性
- var 屬性——決不可能(因為該變量可以隨時被其他代碼修改)
this表達式
Koltin 在作用域這塊有更寬泛的使用,這點在 this 關(guān)鍵字的使用上可以看的很明白援雇,比 java 的 this 使用更靈活椎扬,Kotlin 的 this 關(guān)鍵字可以 + @label 標簽 來指定 this 具體的代表對象
class A { // 隱式標簽 @A
inner class B { // 隱式標簽 @B
fun Int.foo() { // 隱式標簽 @foo
val a = this@A // A 的 this
val b = this@B // B 的 this
val c = this // foo() 的接收者蚕涤,一個 Int
val c1 = this@foo // foo() 的接收者,一個 Int
val funLit = lambda@ fun String.() {
val d = this // funLit 的接收者
}
val funLit2 = { s: String ->
// foo() 的接收者茴丰,因為它包含的 lambda 表達式
// 沒有任何接收者
val d1 = this
}
}
}
}
typealias
看名字可以猜測帶有別名的作用,是的峦椰,typealias 我們可以成為類型別名汰规,聽起來怪怪的,說起來其實很好理解冤竹,可以代理 interface 聲明一個單方法類型的接口鹦蠕,不能寫在 class 內(nèi)在抛,打擊理解一下,寫在 class 內(nèi)不就成了內(nèi)部類了肠阱,typealias 的有點在于可以非常省事的聲明一個類似接口出來
// 在 class 外聲明朴读,作用域和平常類一樣
typealias Click = (String, String) -> Int
class BookData(map: MutableMap<String, Any>) {
fun my(click: Click) {
click("GG", "AA")
}
fun test2() {
// typealias 填參數(shù)時和函數(shù)式對象一樣
my { name, age ->
Log.d("AA", "my 方法參數(shù)傳入")
return@my 10
}
}
}
let / apply / run / with
- let 函數(shù) - 可以對指定對象提供一段代碼的執(zhí)行衅金,返回最后一行的對象,可以不是操作的對象
比如下面這段代碼鉴吹,let 接收我們新建的這個 book 對象惩琉,然后對 book 對象進行了操作,最后一行返回數(shù)據(jù)良蒸,這里可以不寫 return
var book: Book = Book("88").let {
it.name = "-77"
it.sex = "99"
return@let it
}
返回不一樣的數(shù)據(jù)類型伍玖,我們接受 Book 類型的數(shù)據(jù),最后返回 String 類型的數(shù)據(jù)
var name: String = Book("88").let {
it.name = "-77"
it.sex = "99"
return@let it.name
}
我么你還可以結(jié)合 ? 進行對 null 數(shù)據(jù)的操作
Book("88")?.let {
it.name = "-77"
it.sex = "99"
return@let it.name
}
- apply 函數(shù) - 可以對指定對象進行代碼擴展仔燕,然后返回這個對象,注意是這個對象五辽,意味著不能改變對象類型外恕,然后再配合 let 獲取這個對象再進行操作
比如下面這段代碼,apply 內(nèi)的代碼好比就是寫在 Book 類型里面的罪郊,所有屬性剛和方法直接掉尚洽,不像 let 還得寫 let ,這是本質(zhì)的不同
var book: Book = Book("88")
.apply {
name = "-77"
sex = "99"
}
.let {
it.name = "-77"
it.sex = "99"
return@let it
}
let 好比 rxjava 的 map 癣疟,apple 好比 flatmap
- run 函數(shù) - run 和 apple 差不多睛挚,區(qū)別的是 run 返回的不是這個對象急黎,而是最后一行的對象
var name: String = Book("88")
.run {
name = "-77"
sex = "99"
return@run this
}.let {
it.name = "ABB"
return@let it.name
}
- with 函數(shù) - 和 run 一樣叁熔,區(qū)別是寫法不一樣
var name: String = with(Book("88"))
{
name = "-77"
sex = "99"
return@with this
}.let {
it.name = "ABB"
return@let it.name
}
in / out
JAVA 里 List<Object> 是不能轉(zhuǎn)換為 List<String> 的,但是在 koltin 中借助 in / out 就能實現(xiàn)
- Kotlin 中的 out A 類似于 Java 中的 ? extends A遭贸,即泛型參數(shù)類型必須是 A或者 A 的子類心软,用來確定類型的上限
- Kotlin 中的 in A 類似于 Java 中的 ? super A,即泛型參數(shù)類型必須是 B 或者 B 的父類耳贬,用來確定類型的下限
fun copy(from: List<out A>, to: List<in A>) {
for (i in from.indices) {
to[i] = from[i]
}
}
return/break/continue
kotlin 的 return/break/continue 和 java 含義一樣猎唁,但是比 java 擴展的是可以用 @ 標價返回的位置,直接看例子腐魂,比說強
// 1. 和Java不同的是,這些表達式都可作為更大表達式的一部分
val s = person.name ?: return
//2. 和Java不同的是削樊,在 Kotlin 中任何表達式都可以用 標簽@ 來標記
loop@ for (i in 1..100) {
for (j in 1..100) {
if (……) break@loop // 終止loop標記的循環(huán)
if (……) continue@loop // 跳出loop標記的循環(huán)漫贞,繼續(xù)下一次loop標記的循環(huán)
}
}
// 3. 從外層函數(shù)返回
fun foo() {
ints.forEach {
if (it == 0) return // 默認從foo(){}返回
print(it)
}
}
//4. 用顯式標簽從lambda表達式中返回
fun foo() {
ints.forEach lit@ {
if (it == 0) return@lit // 標記從forEach{}返回
print(it)
}
}
// 5. 用隱式標簽(與接收lambda的函數(shù)同名)從lambda表達式中返回
fun foo() {
ints.forEach {
if (it == 0) return@forEach // 隱式標簽forEach育叁,從forEach{}返回
print(it)
}
}
// 6. 用匿名函數(shù)替代lambda表達式:
fun foo() {
ints.forEach(fun(value: Int) {
if (value == 0) return // 從該匿名函數(shù)fun返回
print(value)
})
}