原文章出處:[Kotlin 的 Lambda 表達式,大多數人學得連皮毛都不算](https://kaixue.io/kotlin-lambda/)
## Kotlin 的高階函數
Kotlin很方便,但有時候也讓人頭疼,而且越方便的地方越讓人頭疼,比如 Lambda 表達式。很多人因為 Lambda 而被 Kotlin 吸引,但很多人也因為 Lambda 而被 Kotlin 嚇跑。其實大多數已經用了很久 Kotlin 的人,對 Lambda 也只會簡單使用而已,甚至相當一部分人不靠開發工具的自動補全功能,根本就完全不會寫 Lambda。今天我就來跟大家嘮一嘮 Lambda。不過,要講 Lambda,我們得先從 Kotlin 的高階函數——Higher-Order Function 說起。
在 Java 里,如果你有一個 a 方法需要調用另一個 b 方法,你在里面調用就可以;
~~~kotlin
int a() {
return b(1);
}
a();
~~~
而如果你想在 a 調用時可以動態地設置 b 方法的參數,你就得把參數傳給 a,再從 a 的內部把參數傳給 b:
~~~kotlin
int a(int param) {
return b(param);
}
a(1); // 內部調用 b(1)
a(2); // 內部調用 b(2)
~~~
這都可以做到,不過……如果我想動態設置的不是方法參數,而是方法本身呢?比如我在 a 的內部有一處對別的方法的調用,這個方法可能是 b,可能是 c,不一定是誰,我只知道,我在這里有一個調用,它的參數類型是 int ,返回值類型也是 int ,而具體在 a 執行的時候內部調用哪個方法,我希望可以動態設置:
~~~kotlin
int a(??? method) {
return method(1);
}
a(method1);
a(method2);
~~~
或者說,我想把方法作為參數傳到另一個方法里,這個……可以做到嗎?
不行,也行。在 Java 里是不允許把方法作為參數傳遞的,但是我們**有一個歷史悠久的變通方案:接口**。我們可以通過接口的方式來把方法包裝起來:
~~~java
public interface Wrapper {
int method(int param);
}
~~~
然后把這個接口的類型作為外部方法的參數類型:
~~~java
int a(Wrapper wrapper) {
return wrapper.method(1);
}
~~~
在調用外部方法時,傳遞接口的對象來作為參數:
~~~java
a(wrapper1);
a(wrapper2);
~~~
如果到這里你覺得聽暈了,我換個寫法你再感受一下:
我們在用戶發生點擊行為的時候會觸發點擊事件:
~~~java
// 注:這是簡化后的代碼,不是 View.java 類的源碼
public class View {
OnClickListener mOnClickListener;
...
public void onTouchEvent(MotionEvent e) {
...
mOnClickListener.onClick(this);
...
}
}
~~~
所謂的點擊事件,最核心的內容就是調用內部的一個 OnClickListener 的 onClick() 方法:
~~~java
public interface OnClickListener {
void onClick(View v);
}
~~~
**而所謂的這個 OnClickListener 其實只是一個殼,它的核心全在內部那個 onClick() 方法。換句話說,我們傳過來一個 OnClickListener**:
~~~java
OnClickListener listener1 = new OnClickListener() {
@Override
void onClick(View v) {
doSomething();
}
};
view.setOnClickListener(listener1);
~~~
**本質上其實是傳過來一個可以在稍后被調用的方法(onClick())。只不過因為 Java 不允許傳遞方法,所以我們才把它包進了一個對象里來進行傳遞。
而在 Kotlin 里面,函數的參數也可以是函數類型的**:
~~~kotlin
fun a(funParam: Fun): String {
return funParam(1);
}
~~~
當一個函數含有函數類型的參數的時候——這句話有點繞啊——如果你調用它,你就可以——當然你也必須——傳入一個函數類型的對象給它;
~~~kotlin
fun b(param: Int): String {
return param.toString()
}
a(b)
~~~
不過在具體的寫法上并沒有我的示例這么粗暴。
首先我寫的這個 Fun 作為函數類型其實是錯的,Kotlin 里并沒有這么一種類型來標記這個變量是個「函數類型」。因為函數類型不是一「個」類型,而是一「類」類型,**因為函數類型可以有各種各樣不同的參數和返回值的類型的搭配,這些搭配屬于不同的函數類型**。例如,無參數無返回值(() -> Unit)和單 Int 型參數返回 String (Int -> String)是兩種不同的類型,這個很好理解,就好像 Int 和 String 是兩個不同的類型。所以**不能只用 Fun 這個詞來表示「這個參數是個函數類型」,就好像不能用 Class 這個詞來表示「這個參數是某個類」,因為你需要指定,具體是哪種函數類型,或者說這個函數類型的參數,它的參數類型是什么、返回值類型是什么,而不能籠統地一句說「它是函數類型」就完了**。
所以**對于函數類型的參數,你要指明它有幾個參數、參數的類型是什么以及返回值類型是什么**,那么寫下來就大概是這個樣子:
~~~kotlin
fun a(funParam: (Int) -> String): String {
return funParam(1)
}
~~~
看著有點可怕。但是只有這樣寫,調用的人才知道應該傳一個怎樣的函數類型的參數給你。
同樣的,函數類型不只可以作為函數的參數類型,還可以作為函數的返回值類型:
~~~kotlin
fun c(param: Int): (Int) -> Unit {
...
}
~~~
**這種「參數或者返回值為函數類型的函數」,在 Kotlin 中就被稱為「高階函數」**——Higher-Order Functions。
這個所謂的「高階」,總給人一種神秘感:階是什么?哪里高了?其實沒有那么復雜,高階函數這個概念源自數學中的高階函數。在數學里,如果一個函數使用函數作為它的參數或者結果,它就被稱作是一個「高階函數」。比如求導就是一個典型的例子:你對 f(x) = x 這個函數求導,結果是 1;對 f(x) = x2 這個函數求導,結果是 2x。很明顯,求導函數的參數和結果都是函數,其中 f(x) 的導數是 1 這其實也是一個函數,只不過是一個結果恒為 1 的函數,所以——啊講岔了,總之,**Kotlin 里,這種參數有函數類型或者返回值是函數類型的函數,都叫做高階函數**,這只是個對這一類函數的稱呼,沒有任何特殊性,Kotlin 的高階函數沒有任何特殊功能,這是我想說的。
另外,除了作為函數的參數和返回值類型,你把它賦值給一個變量也是可以的。
**不過對于一個聲明好的函數,不管是你要把它作為參數傳遞給函數,還是要把它賦值給變量,都得在函數名的左邊加上雙冒號才行**:
~~~kotlin
a(::b)
val d = ::b
~~~
這……是為什么呢?
## 雙冒號 ::method 到底是什么?
如果你上網搜,你會看到這個雙冒號的寫法叫做函數引用 Function Reference,這是 Kotlin 官方的說法。但是這又表示什么意思?表示它指向上面的函數?那既然都是一個東西,為什么不直接寫函數名,而要加兩個冒號呢?
**因為加了兩個冒號,這個函數才變成了一個對象**。
什么意思?
**Kotlin 里「函數可以作為參數」這件事的本質,是函數在 Kotlin 里可以作為對象存在——因為只有對象才能被作為參數傳遞啊。賦值也是一樣道理,只有對象才能被賦值給變量啊。但 Kotlin 的函數本身的性質又決定了它沒辦法被當做一個對象。那怎么辦呢?Kotlin 的選擇是,那就創建一個和函數具有相同功能的對象。怎么創建?使用雙冒號。
在 Kotlin 里,一個函數名的左邊加上雙冒號,它就不表示這個函數本身了,而表示一個對象,或者說一個指向對象的引用,但,這個對象可不是函數本身,而是一個和這個函數具有相同功能的對象**。
怎么個相同法呢?你可以怎么用函數,就能怎么用這個加了雙冒號的對象:
~~~kotlin
b(1) // 調用函數
d(1) // 用對象 a 后面加上括號來實現 b() 的等價操作
(::b)(1) // 用對象 :b 后面加上括號來實現 b() 的等價操作
~~~
但我再說一遍,**這個雙冒號的這個東西,它不是一個函數,而是一個對象,一個函數類型的對象**。
**對象是不能加個括號來調用的,對吧?但是函數類型的對象可以。為什么?因為這其實是個假的調用,它是 Kotlin 的語法糖,實際上你對一個函數類型的對象加括號、加參數,它真正調用的是這個對象的 invoke() 函數**:
~~~kotlin
d(1) // 實際上會調用 d.invoke(1)
(::b)(1) // 實際上會調用 (::b).invoke(1)
~~~
所以**你可以對一個函數類型的對象調用 invoke(),但不能對一個函數這么做**:
~~~kotlin
b.invoke(1) // 報錯
~~~
為什么?因為**只有函數類型的對象有這個自帶的 invoke() 可以用,而函數,不是函數類型的對象**。那它是什么類型的?它什么類型也不是。**函數不是對象,它也沒有類型,函數就是函數,它和對象是兩個維度的東西**。
**包括雙冒號加上函數名的這個寫法,它是一個指向對象的引用,但并不是指向函數本身,而是指向一個我們在代碼里看不見的對象。這個對象復制了原函數的功能,但它并不是原函數**。
> 這個……是底層的邏輯,但我知道這個有什么用呢?
這個知識能幫你解開 Kotlin 的高階函數以及接下來我馬上要講的匿名函數、Lambda 相關的大部分迷惑。
比如我在代碼里有這么幾行:
~~~kotlin
fun b(param: Int): String {
return param.toString()
}
val d = ::b
~~~
那我如果想把 d 賦值給一個新的變量 e:
~~~kotlin
val e = d
~~~
我等號右邊的 d,應該加雙冒號還是不加呢?
不用試,也不用搜,想一想:這是個賦值操作對吧?賦值操作的右邊是個對象對吧?d 是對象嗎?當然是了,b 不是對象是因為它來自函數名,但 **d 已經是個對象了,所以直接寫就行了**。
## 匿名函數
我們繼續講。
**要傳一個函數類型的參數,或者把一個函數類型的對象賦值給變量,除了用雙冒號來拿現成的函數使用,你還可以直接把這個函數挪過來寫**:
~~~kotlin
a(fun b(param: Int): String {
return param.toString()
});
val d = fun b(param: Int): String {
return param.toString()
}
~~~
另外,這種寫法的話,函數的名字其實就沒用了,所以你可以把它省掉:
~~~kotlin
a(fun(param: Int): String {
return param.toString()
});
val d = fun(param: Int): String {
return param.toString()
}
~~~
這種寫法叫做**匿名函數**。為什么叫匿名函數?很簡單,**因為它沒有名字唄,對吧。等號左邊的不是函數的名字啊,它是變量的名字。這個變量的類型是一種函數類型,具體到我們的示例代碼來說是一種只有一個參數、參數類型是 Int、并且返回值類型為 String 的函數類型**。
另外呢,**其實剛才那種左邊右邊都有名字的寫法,Kotlin 是不允許的。右邊的函數既然要名字也沒有用,Kotlin 干脆就不許它有名字了**。
所以,如果你在 Java 里設計一個回調的時候是這么設計的:
~~~java
public interface OnClickListener {
void onClick(View v);
}
public void setOnClickListener(OnClickListener listener) {
this.listener = listener;
}
~~~
使用的時候是這么用的:
~~~java
view.setOnClickListener(new OnClickListener() {
@Override
void onClick(View v) {
switchToNextPage();
}
});
~~~
到了 Kotlin 里就可以改成這么寫了:
~~~kotlin
fun setOnClickListener(onClick: (View) -> Unit) {
this.onClick = onClick
}
view.setOnClickListener(fun(v: View): Unit) {
switchToNextPage()
})
~~~
簡單一點哈?另外**大多數(幾乎所有)情況下,匿名函數還能更簡化一點,寫成 Lambda 表達式的形式**:
~~~kotlin
view.setOnClickListener({ v: View ->
switchToNextPage()
})
~~~
## Lambda 表達式
終于講到 Lambda 了。
如果 Lambda 是函數的最后一個參數,你可以把 Lambda 寫在括號的外面:
~~~kotlin
view.setOnClickListener() { v: View ->
switchToNextPage()
}
~~~
而如果 Lambda 是函數唯一的參數,你還可以直接把括號去了:
~~~kotlin
view.setOnClickListener { v: View ->
switchToNextPage()
}
~~~
另外,如果這個 Lambda 是單參數的,它的這個參數也省略掉不寫:
~~~kotlin
view.setOnClickListener {
switchToNextPage()
}
~~~
哎,**不錯,單參數的時候只要不用這個參數就可以直接不寫了。
其實就算用,也可以不寫,因為 Kotlin 的 Lambda 對于省略的唯一參數有默認的名字:it**:
~~~kotlin
view.setOnClickListener {
switchToNextPage()
it.setVisibility(GONE)
}
~~~
有點爽哈?不過我們先停下想一想:這個 Lambda 這也不寫那也不寫的……它不迷茫嗎?**它是怎么知道自己的參數類型和返回值類型的?**
**靠上下文的推斷**。我調用的函數在聲明的地方有明確的參數信息吧?
~~~kotlin
fun setOnClickListener(onClick: (View) -> Unit) {
this.onClick = onClick
}
~~~
這里面把這個參數的參數類型和返回值寫得清清楚楚吧?所以 Lambda 才不用寫的。
所以,**當你要把一個匿名函數賦值給變量而不是作為函數參數傳遞的時候**:
~~~kotlin
val b = fun(param: Int): String {
return param.toString()
}
~~~
如果也簡寫成 Lambda 的形式:
~~~kotlin
val b = { param: Int ->
return param.toString()
}
~~~
**就不能省略掉 Lambda 的參數類型了**:
~~~kotlin
val b = {
return it.toString() // it 報錯
}
~~~
為什么?因為**它無法從上下文中推斷出這個參數的類型啊**!
**如果你出于場景的需求或者個人偏好,就是想在這里省掉參數類型,那你需要給左邊的變量指明類型**:
~~~kotlin
val b: (Int) -> String = {
return it.toString() // it 可以被推斷出是 Int 類型
}
~~~
另外 **Lambda 的返回值不是用 return 來返回,而是直接取最后一行代碼的值**:
~~~kotlin
val b: (Int) -> String = {
it.toString() // it 可以被推斷出是 Int 類型
}
~~~
這個一定注意,**Lambda 的返回值別寫 return,如果你寫了,它會把這個作為它外層的函數的返回值來直接結束外層函數。當然如果你就是想這么做那沒問題啊,但如果你是只是想返回 Lambda,這么寫就出錯了**。
另外因為**Lambda 是個代碼塊,它總能根據最后一行代碼來推斷出返回值類型,所以它的返回值類型確實可以不寫**。實際上,Kotlin 的 Lambda 也是寫不了返回值類型的,語法上就不支持。
現在我再停一下,我們想想:匿名函數和 Lambda……它們到底是什么?
## Kotlin 里匿名函數和 Lambda 表達式的本質
我們先看匿名函數。它可以作為參數傳遞,也可以賦值給變量,對吧?
但是我們剛才也說過了函數是不能作為參數傳遞,也不能賦值給變量的,對吧?
那為什么匿名函數就這么特殊呢?
因為 **Kotlin 的匿名函數不是函數。它是個對象。匿名函數雖然名字里有「函數」兩個字,包括英文的原名也是 Anonymous Function,但它其實不是函數,而是一個對象,一個函數類型的對象。它和雙冒號加函數名是一類東西,和函數不是**。
所以,你才可以直接把它當做函數的參數來傳遞以及賦值給變量:
~~~kotlin
a(fun (param: Int): String {
return param.toString()
});
val a = fun (param: Int): String {
return param.toString()
}
~~~
同理,**Lambda 其實也是一個函數類型的對象而已**。你能怎么使用雙冒號加函數名,就能怎么使用匿名函數,以及怎么使用 Lambda 表達式。
這,就是 **Kotlin 的匿名函數和 Lambda 表達式的本質,它們都是函數類型的對象**。Kotlin 的 Lambda 跟 Java 8 的 Lambda 是不一樣的,Java 8 的 Lambda 只是一種便捷寫法,本質上并沒有功能上的突破,而 Kotlin 的 Lambda 是實實在在的對象。
在你知道了在 Kotlin 里「函數并不能傳遞,傳遞的是對象」和「匿名函數和 Lambda 表達式其實都是對象」這些本質之后,你以后去寫 Kotlin 的高階函數會非常輕松非常舒暢。
**Kotlin 官方文檔里對于雙冒號加函數名的寫法叫 Function Reference 函數引用,故意引導大家認為這個引用是指向原函數的,這是為了簡化事情的邏輯,讓大家更好上手 Kotlin**;但這種邏輯是有毒的,一旦你信了它,你對于匿名函數和 Lambda 就怎么也搞不清楚了。
## 對比 Java 的 Lambda
再說一下 Java 的 Lambda。對于 Kotlin 的 Lambda,有很多從 Java 過來的人表示「好用好用但不會寫」。這是一件很有意思的事情:你都不會寫,那你是怎么會用的呢?Java 從 8 開始引入了對 Lambda 的支持,對于單抽象方法的接口——簡稱 SAM 接口,Single Abstract Method 接口——對于這類接口,Java 8 允許你用 Lambda 表達式來創建匿名類對象,但它本質上還是在創建一個匿名類對象,只是一種簡化寫法而已,所以 Java 的 Lambda 只靠代碼自動補全就基本上能寫了。而 Kotlin 里的 Lambda 和 Java 本質上就是不同的,因為 Kotlin 的 Lambda 是實實在在的函數類型的對象,功能更強,寫法更多更靈活,所以很多人從 Java 過來就有點搞不明白了。
另外呢,**Kotlin 是不支持使用 Lambda 的方式來簡寫匿名類對象的**,因為我們有函數類型的參數嘛,所以這種單函數接口的寫法就直接沒必要了。那你還支持它干嘛?
**不過當和 Java 交互的時候,Kotlin 是支持這種用法的**:當你的函數參數是 Java 的單抽象方法的接口的時候,你依然可以使用 Lambda 來寫參數。但這其實也不是 Kotlin**增加**了功能,而是對于來自 Java 的單抽象方法的接口,Kotlin 會為它們額外創建一個把參數替換為函數類型的橋接方法,讓你可以間接地創建 Java 的匿名類對象。
這就是為什么,你會發現當你在 Kotlin 里調用 View.java 這個類的 setOnClickListener() 的時候,可以傳 Lambda 給它來創建 OnClickListener 對象,但你照著同樣的寫法寫一個 Kotlin 的接口,你卻不能傳 Lambda。因為 Kotlin 期望我們直接使用函數類型的參數,而不是用接口這種折中方案。
## 總結
好,這就是 Kotlin 的高階函數、匿名函數和 Lambda。簡單總結一下:
* 在 Kotlin 里,有一類 Java 中不存在的類型,叫做「函數類型」,這一類類型的對象在可以當函數來用的同時,還能作為函數的參數、函數的返回值以及賦值給變量;
* **創建一個函數類型的對象有三種方式:雙冒號加函數名、匿名函數和 Lambda**;
* 一定要記住:**雙冒號加函數名、匿名函數和 Lambda 本質上都是函數類型的對象。在 Kotlin 里,匿名函數不是函數,是函數類型的對象**,Lambda 也不是什么玄學的所謂「它只是個代碼塊,沒法歸類」,**Kotlin 的 Lambda 可以歸類,它屬于函數類型的對象**。
- 前言
- Kotlin簡介
- IntelliJ IDEA技巧總結
- idea設置類注釋和方法注釋模板
- 像Android Studion一樣創建工程
- Gradle
- Gradle入門
- Gradle進階
- 使用Gradle創建一個Kotlin工程
- 環境搭建
- Androidstudio平臺搭建
- Eclipse的Kotlin環境配置
- 使用IntelliJ IDEA
- Kotlin學習路線
- Kotlin官方中文版文檔教程
- 概述
- kotlin用于服務器端開發
- kotlin用于Android開發
- kotlin用于JavaScript開發
- kotlin用于原生開發
- Kotlin 用于數據科學
- 協程
- 多平臺
- 新特性
- 1.1的新特性
- 1.2的新特性
- 1.3的新特性
- 開始
- 基本語法
- 習慣用法
- 編碼規范
- 基礎
- 基本類型
- 包與導入
- 控制流
- 返回與跳轉
- 類與對象
- 類與繼承
- 屬性與字段
- 接口
- 可見性修飾符
- 擴展
- 數據類
- 密封類
- 泛型
- 嵌套類
- 枚舉類
- 對象
- 類型別名
- 內嵌類
- 委托
- 委托屬性
- 函數與Lambda表達式
- 函數
- Lambda表達式
- 內聯函數
- 集合
- 集合概述
- 構造集合
- 迭代器
- 區間與數列
- 序列
- 操作概述
- 轉換
- 過濾
- 加減操作符
- 分組
- 取集合的一部分
- 取單個元素
- 排序
- 聚合操作
- 集合寫操作
- List相關操作
- Set相關操作
- Map相關操作
- 多平臺程序設計
- 平臺相關聲明
- 以Gradle創建
- 更多語言結構
- 解構聲明
- 類型檢測與轉換
- This表達式
- 相等性
- 操作符重載
- 空安全
- 異常
- 注解
- 反射
- 作用域函數
- 類型安全的構造器
- Opt-in Requirements
- 核心庫
- 標準庫
- kotlin.test
- 參考
- 關鍵字與操作符
- 語法
- 編碼風格約定
- Java互操作
- Kotlin中調用Java
- Java中調用Kotlin
- JavaScript
- 動態類型
- kotlin中調用JavaScript
- JavaScript中調用kotlin
- JavaScript模塊
- JavaScript反射
- JavaScript DCE
- 原生
- 并發
- 不可變性
- kotlin庫
- 平臺庫
- 與C語言互操作
- 與Object-C及Swift互操作
- CocoaPods集成
- Gradle插件
- 調試
- FAQ
- 協程
- 協程指南
- 基礎
- 取消與超時
- 組合掛起函數
- 協程上下文與調度器
- 異步流
- 通道
- 異常處理與監督
- 共享的可變狀態與并發
- Select表達式(實驗性)
- 工具
- 編寫kotlin代碼文檔
- 使用Kapt
- 使用Gradle
- 使用Maven
- 使用Ant
- Kotlin與OSGI
- 編譯器插件
- 編碼規范
- 演進
- kotlin語言演進
- 不同組件的穩定性
- kotlin1.3的兼容性指南
- 常見問題
- FAQ
- 與Java比較
- 與Scala比較(官方已刪除)
- Google開發者官網簡介
- Kotlin and Android
- Get Started with Kotlin on Android
- Kotlin on Android FAQ
- Android KTX
- Resources to Learn Kotlin
- Kotlin樣品
- Kotlin零基礎到進階
- 第一階段興趣入門
- kotlin簡介和學習方法
- 數據類型和類型系統
- 入門
- 分類
- val和var
- 二進制基礎
- 基礎
- 基本語法
- 包
- 示例
- 編碼規范
- 代碼注釋
- 異常
- 根類型“Any”
- Any? 可空類型
- 可空性的實現原理
- kotlin.Unit類型
- kotlin.Nothing類型
- 基本數據類型
- 數值類型
- 布爾類型
- 字符型
- 位運算符
- 變量和常量
- 語法和運算符
- 關鍵字
- 硬關鍵字
- 軟關鍵字
- 修飾符關鍵字
- 特殊標識符
- 操作符和特殊符號
- 算術運算符
- 賦值運算符
- 比較運算符
- 邏輯運算符
- this關鍵字
- super關鍵字
- 操作符重載
- 一元操作符
- 二元操作符
- 字符串
- 字符串介紹和屬性
- 字符串常見方法操作
- 字符串模板
- 數組
- 數組介紹創建及遍歷
- 數組常見方法和屬性
- 數組變化以及下標越界問題
- 原生數組類型
- 區間
- 正向區間
- 逆向區間
- 步長
- 類型檢測與類型轉換
- is、!is、as、as-運算符
- 空安全
- 可空類型變量
- 安全調用符
- 非空斷言
- Elvis操作符
- 可空性深入
- 可空性和Java
- 函數
- 函數式編程概述
- OOP和FOP
- 函數式編程基本特性
- 組合與范疇
- 在Kotlin中使用函數式編程
- 函數入門
- 函數作用域
- 函數加強
- 命名參數
- 默認參數
- 可變參數
- 表達式函數體
- 頂層、嵌套、中綴函數
- 尾遞歸函數優化
- 函數重載
- 控制流
- if表達式
- when表達式
- for循環
- while循環
- 循環中的 Break 與 continue
- return返回
- 標簽處返回
- 集合
- list集合
- list集合介紹和操作
- list常見方法和屬性
- list集合變化和下標越界
- set集合
- set集合介紹和常見操作
- set集合常見方法和屬性
- set集合變換和下標越界
- map集合
- map集合介紹和常見操作
- map集合常見方法和屬性
- map集合變換
- 集合的函數式API
- map函數
- filter函數
- “ all ”“ any ”“ count ”和“ find ”:對集合應用判斷式
- 別樣的求和方式:sumBy、sum、fold、reduce
- 根據人的性別進行分組:groupBy
- 扁平化——處理嵌套集合:flatMap、flatten
- 惰性集合操作:序列
- 區間、數組、集合之間轉換
- 面向對象
- 面向對象-封裝
- 類的創建及屬性方法訪問
- 類屬性和字段
- 構造器
- 嵌套類(內部類)
- 枚舉類
- 枚舉類遍歷&枚舉常量常用屬性
- 數據類
- 密封類
- 印章類(密封類)
- 面向對象-繼承
- 類的繼承
- 面向對象-多態
- 抽象類
- 接口
- 接口和抽象類的區別
- 面向對象-深入
- 擴展
- 擴展:為別的類添加方法、屬性
- Android中的擴展應用
- 優化Snackbar
- 用擴展函數封裝Utils
- 解決煩人的findViewById
- 擴展不是萬能的
- 調度方式對擴展函數的影響
- 被濫用的擴展函數
- 委托
- 委托類
- 委托屬性
- Kotlin5大內置委托
- Kotlin-Object關鍵字
- 單例模式
- 匿名類對象
- 伴生對象
- 作用域函數
- let函數
- run函數
- with函數
- apply函數
- also函數
- 標準庫函數
- takeIf 與 takeUnless
- 第二階段重點深入
- Lambda編程
- Lambda成員引用高階函數
- 高階函數
- 內聯函數
- 泛型
- 泛型的分類
- 泛型約束
- 子類和子類型
- 協變與逆變
- 泛型擦除與實化類型
- 泛型類型參數
- 泛型的背后:類型擦除
- Java為什么無法聲明一個泛型數組
- 向后兼容的罪
- 類型擦除的矛盾
- 使用內聯函數獲取泛型
- 打破泛型不變
- 一個支持協變的List
- 一個支持逆變的Comparator
- 協變和逆變
- 第三階段難點突破
- 注解和反射
- 聲明并應用注解
- DSL
- 協程
- 協程簡介
- 協程的基本操作
- 協程取消
- 管道
- 慕課霍丙乾協程筆記
- Kotlin與Java互操作
- 在Kotlin中調用Java
- 在Java中調用Kotlin
- Kotlin與Java中的操作對比
- 第四階段專題練習
- 朱凱Kotlin知識點總結
- Kotlin 基礎
- Kotlin 的變量、函數和類型
- Kotlin 里那些「不是那么寫的」
- Kotlin 里那些「更方便的」
- Kotlin 進階
- Kotlin 的泛型
- Kotlin 的高階函數、匿名函數和 Lambda 表達式
- Kotlin協程
- 初識
- 進階
- 深入
- Kotlin 擴展
- 會寫「18.dp」只是個入門——Kotlin 的擴展函數和擴展屬性(Extension Functions / Properties)
- Kotlin實戰-開發Android