以一個java
老鳥的角度,如何去看 kotlin
。 Java源代碼應該如何用Kotlin重構。 如何正確學習kotlin而且應用到實際開發中。本文將會探究。java
本文分兩大塊,重難點和潛規則。android
重難點:Kotlin中能夠獨立出來說解的大塊知識點。提供單獨Demo。這部分大多數是Kotlin開創的新概念(相比於Java)。安全
潛規則:Kotlin是谷歌用來替換Java的,它和java百分百徹底兼容,可是實際上java轉成kotlin以後,須要咱們手動修改不少東西,甚至某些部分必須打散重構來達到最優編碼。其中,kotlin的某些特性和java不一樣,甚至徹底反轉。這部分知識點比較零碎,單獨Demo不方便提供,就以小例子的形式來寫。markdown
重難點app
Kotlin
泛型潛規則框架
Kotlin文件和類不存在一對一關係less
共生體ide
繼承函數
修飾符學習
空指針問題
lambda表達式是JDK1.8提出的,目的是爲了改善Java中大量出現的只有一個方法的回調函數的累贅寫法。這裏不討論Java的lambda. Kotlin中lambda已經融入代碼核心,而不是像java同樣是個註解+語法糖。
基礎:
一個lambda表達式如圖:
lambda 表達式的4個部分
這是一個kotlin文件:
/** * 好比說這樣,我要給這個doFirst方法傳一個執行過程,類型就是一個輸入2個Int,輸出一個Int的拉姆達表達式 */
fun calculate(p1: Int, p2: Int, event1: (Int, Int) -> Int, event2: (Int, Int) -> Int) {
println("執行doFirst")
println("執行event1 ${event1(p1, p2)}")
println("執行event2 ${event2(p1, p2)}")
}
//測試lambda表達式
fun main() {
val sum = { x: Int, y: Int ->
print("求和 ")
x + y
}
val diff = { x: Int, y: Int ->
print("求差 ")
x - y
}
//kotlin裏面,能夠把拉姆達表示看成一個普通變量同樣,去傳遞實參
calculate(p1 = 1, p2 = 2, event1 = sum, event2 = diff)
}
複製代碼
定義了一個calculate函數, p1,p2 是Int,而event1和event2 則是 lambda表達式. 高級語言特性,一等函數公民:函數自己能夠被看成普通參數同樣去傳遞,而且調用。那麼, kotlin的lambda內核是怎麼樣的?
上圖可以看出,
而看了Function2的源碼,簡直亮瞎了個人鈦合金狗眼:
Function.kt文件中:
Function接口,Function2接口.....Function22接口。好了,不要質疑谷歌大佬的設計思路,反正大佬寫的就是對的...這裏用22個接口(至於爲何是22個,猜測是谷歌以爲不會有哪一個腦子秀逗的開發者真的弄22個以上的參數擠在一塊兒吧),表示kotlin開發中可能出現的lambda表達式參數列表。
給一個Button 設置點擊事件,kotlin的寫法是:
val btn = Button(this)
val lis = View.OnClickListener { println("111") }
btn.setOnClickListener(lis)
複製代碼
或者:
val btn = Button(this)
btn.setOnClickListener { println("111") }
複製代碼
setOnClickListener 方法的參數是 OnClickListener 接口:
public interface OnClickListener {
void onClick(View v);
}
複製代碼
相似這種符合lambda表達式特徵的接口,均可以使用上述兩種寫法來大大簡化代碼量。
不得不提一句,lambda表達式有一個變形,那就是:當lambda表達式做爲方法的最後一個參數時,能夠lambda表達式放到小括號外面。而若是隻有一個參數就是lambda表達式,那麼括號能夠省略
這個很是重要,不瞭解這個,不少地方都會感受很蛋疼。
fun testLambda(s: String, block: () -> String) {
println(s)
block()
}
fun testLambda2(block: () -> String) {
block()
}
fun main() {
testLambda("第一個參數") {
println("block函數體")
"返回值"
}
testLambda2 {
println("block函數體")
"返回值"
}
}
複製代碼
Kotlin中lambda表達式能夠看成普通參數同樣去傳遞,去賦值,去使用。
上文提到,kotlin中lambda表達式已經融入了語言核心,而具體的體現就是高階函數,把lambda表達式看成參數去使用. 這種將lambda表達式做爲參數或者返回值的函數,就叫高階函數。
Kotlin谷歌已經給咱們封裝了一些高階函數。
代碼以下(這裏爲了展現代碼全貌,忽視androidStudio的代碼優化提示):
class A {
val a = 1
val b = "b"
}
fun testRun() {
//run方法有兩種用法,一個是不依賴對象,也就是做爲全局函數
run<String> {//我能夠規定返回值的類型
println("我是全局函數")
"返回值"
}
val a = A()
//另外一種則是 依賴對象
a.run<A,String> {//這裏一樣能夠規定返回值的類型
println(this.a)
println(this.b)
"返回值"
}
}
fun main() {
testRun()
}
複製代碼
如上所示:
run函數分爲兩類
不依賴對象的全局函數。
依賴對象的"相似"擴展函數。
二者均可以規定返回值類型(精通泛型的話,這裏應該不難理解,泛型下一節詳解)。
閱讀源碼:
@kotlin.internal.InlineOnly
public inline fun <R> run(block: () -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
複製代碼
run函數被重載,參數有所不一樣
因此,前者不須要依賴對象,後者必須依賴對象(由於它是T類的"擴展函數")
使用場景
根據run方法的特性,不管是否是依賴對象,它都是封裝了一段代碼,並且仍是inline的。因此:
若是你不想把一段代碼獨立成方法,又不想讓它們看上去這麼凌亂,最重要的是告訴後來的開發者 這段代碼是一個總體,不要隨便給我在裏面插代碼,或者拆分。那就用run方法,把他們整合到一個做用域中。
run {
println("這是一段代碼邏輯很相近的代碼段")
println("可是我不想把他獨立成一個方法")
println("又擔憂別人會隨便改")
println("因此用run方法把他們放在同一個做用域中")
println("小組中其餘人看到這段,就知道不要把無關代碼插進來")
}
複製代碼
更神奇的是,這個run函數是有返回值的,參數返回值能夠利用起來:
fun testRun2(param1: String, param2: String, param3: String) {
//我想讓這幾個參數都不爲空,若是檢查是空,就不執行方法主體
val checkRes: Boolean = run<Boolean> {
when {
param1.isNullOrEmpty() -> {
false
}
param2.isNullOrEmpty() -> {
false
}
param3.isNullOrEmpty() -> {
false
}
else -> true
}
}
if (checkRes){
println("參數檢查完畢,如今能夠繼續接下來的操做了")
}else{
println("參數檢查不經過,不執行主體代碼")
}
}
fun main() {
testRun2("1", "2", "")
}
複製代碼
main運行結果:
參數檢查完畢,如今能夠繼續接下來的操做了
小結論
run方法在整合小段代碼的功效上,仍是很實用的
上面列舉出來的這些系統高階函數原理上都差很少,只是使用場景有區別,所以除了run以外,其餘的再也不詳解,而只說明使用場景。
和run只有一個區別,run是返回block()執行以後的返回值,而,apply 則是返回this,所以 apply必須依賴對象。而因爲返回了this,所以能夠連續apply調用。
fun testApply() {
val a = A()
a.apply {
println("若是一個對象要對它進行多個階段的處理")
}.apply {
println("那麼多個階段都擠在一塊兒則容易混淆,")
}.apply {
println("此時能夠用apply將每個階段分開擺放")
}.apply {
println("讓程序代碼更加優雅")
}
}
fun main() {
testApply()
}
複製代碼
下面的代碼應該都很眼熟,Glide圖片加載框架的用法,with(context)而後鏈式調用
Glide.with(this).load(image).asGif().into(mImageView);
複製代碼
Kotlin中的with貌似把這一寫法發揚光大了(只是類比,不用深究),場景以下:
class A {
val a = 1
val b = "b"
fun showA() {
println("$a")
}
fun showB() {
println("$a $b")
}
}
fun testWith() {
val a = A()
with(a) {
println("做用域中能夠直接引用建立出的a對象")
this.a
this.b
this.showA()
this
}.showB()
}
fun main() {
testWith()
}
複製代碼
細節
also和with同樣,必須依賴對象,返回值爲this。所以它也支持鏈式調用,它和apply的區別是:
apply的block,沒有參數,可是 also 則將this做爲了參數。這麼作形成的差別是:
做用域 { } 內調用當前對象的方式不一樣。
class A {
val a = 1
val b = "b"
fun showA() {
println("$a")
}
fun showB() {
println("$a $b")
}
}
fun testApply() {
A().apply {
this.showA()
println("=======")
}.showB()
}
fun testAlso() {
A().also {
it.showA()
println("=======")
}.showB()
}
複製代碼
apply 必須用this.xxx
, also則用 it.xxx
.
類比到run:
public inline fun <T, R> T.run(block: T.() -> R): R {
return block()
}
public inline fun <T, R> T.let(block: (T) -> R): R {
return block(this)
}
複製代碼
只有一個區別:run的block執行不須要參數,而let 的block執行時須要傳入this。
形成差別爲:
A().run {
println("最後一行爲返回值")
this
}.showA()
A().let {
println("最後一行爲返回值")
it
}.showA()
複製代碼
run{} 做用域中 只能經過this.xxx操做當前對象,let 則用 it.xxx
這兩個做用相反,而且他們必須依賴對象。看源碼:
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? {
return if (predicate(this)) this else null
}
public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? {
return if (!predicate(this)) this else null
}
複製代碼
predicate 是 (T)->Boolean 類型的lambda表達式,表示斷言判斷,若是判斷爲true,則返回自身,不然返回空
class A {
val a = 0
val b = "b"
fun showA() {
println("$a")
}
fun showB() {
println("$a $b")
}
}
fun testTakeIfAndTakeUnless() {
println("test takeIf")
A().takeIf { it.a > 0 }?.showB()
println("==========")
println("test takeUnless")
A().takeUnless { it.a > 0 }?.showB()
}
fun main() {
testTakeIfAndTakeUnless()
}
複製代碼
執行結果:
test takeIf
==========
test takeUnless
0 b
複製代碼
takeIf / takeUnless
適用於將條件判斷的代碼包在一個做用域{}中,而後 用 ?.xxxx的安全調用符 來 執行對象操做。
repeat是 屢次循環的傻瓜版寫法。
fun testRepeat() {
repeat(10) {
print("$it ")
}
}
fun main() {
testRepeat()
}
複製代碼
執行結果:
0 1 2 3 4 5 6 7 8 9
複製代碼
lazy的做用是: 延遲初始化val定義的常量。
class B {
val i: Int by lazy {
println("執行i初始化")
20
}
init {
println("構造函數執行")
}
}
複製代碼
若是隻是初始化B對象,卻沒有使用到變量i
, 那麼延遲初始化不會執行。
fun main() {
B()
}
複製代碼
執行結果:
構造函數執行
複製代碼
而若是使用到了變量i
,纔會去在調用以前初始化:
fun main() {
println("i 變量的值是:" + B().i)
}
複製代碼
執行結果:
構造函數執行
執行i初始化
i 變量的值是:20
複製代碼
Kotlin官方提供的高階函數,run,apply,also,let,with等,旨在使用**{}做用域**,將聯繫緊密的代碼封在一個做用域內,讓一個方法內部顯得 有條不紊,閱讀觀感更好,可維護性更高,代碼更優雅。上述,除了lazy以外,全部的 函數都在Standart.kt文件內部。