本篇文章主要介紹以下幾個(gè)知識(shí)點(diǎn):
- Kotlin 語(yǔ)言簡(jiǎn)介
- 變量和函數(shù)
- 程序的邏輯控制
- 面向?qū)ο缶幊?/li>
- Lambda 編程
- 空指針檢查
- Kotlin 小技巧
內(nèi)容參考自第一行代碼第3版

1. Kotlin 語(yǔ)言簡(jiǎn)介
編程語(yǔ)言大致可分為兩類:編譯型語(yǔ)言和解釋型語(yǔ)言。
編譯型:編譯器將編寫(xiě)的源代碼一次性地編譯成計(jì)算機(jī)可識(shí)別的二進(jìn)制文件,然后計(jì)算機(jī)直接執(zhí)行,如 C、C++。
解釋型:程序運(yùn)行時(shí),解釋器會(huì)一行行讀取編寫(xiě)的源代碼,然后實(shí)時(shí)地將這些源代碼解釋成計(jì)算機(jī)可識(shí)別的二進(jìn)制數(shù)據(jù)后再執(zhí)行,如 Python、JavaScript(解釋型語(yǔ)言效率會(huì)差些)。
Java 先編譯再運(yùn)行,但 Java 代碼編譯之后生成的是 class 文件,只有 Java 虛擬機(jī)才能識(shí)別,Java 虛擬機(jī)將編譯后的 class 文件解釋成二進(jìn)制數(shù)據(jù)后再執(zhí)行,因而 Java 屬于解釋型語(yǔ)言。
Kotlin 也是通過(guò)編譯器編譯成 class 文件,從而 Java 虛擬機(jī)可以識(shí)別。
2. 變量和函數(shù)
2.1 變量
Kotlin 定義一個(gè)變量,只允許在變量前聲明兩種關(guān)鍵字:val 和 var。
val (value的簡(jiǎn)寫(xiě))聲明不可變的變量,對(duì)應(yīng) java 中的 final 變量。
var (variable的簡(jiǎn)寫(xiě))聲明可變的變量,對(duì)應(yīng) java 中的非 final變量。
使用技巧:編程時(shí)優(yōu)先使用 val 來(lái)聲明變量,當(dāng) va l無(wú)法滿足需求時(shí)再使用 var。
2.2 函數(shù)
fun (function的簡(jiǎn)寫(xiě)) 是定義函數(shù)的關(guān)鍵字
如定義個(gè) 返回兩個(gè)數(shù)中較大的數(shù) 的函數(shù)如下:
fun main() {
val a = 10
val b = 20
val value = largerNum(a, b)
print("large number is $value")
}
fun largerNum(num1: Int, num2: Int): Int {
return max(num1, num2)
}
3. 程序的邏輯控制
程序的執(zhí)行語(yǔ)句主要分 3 種:順序、條件和循環(huán)語(yǔ)句。
kotlin 中的條件語(yǔ)句主要用 if 和 when 語(yǔ)句,循環(huán)語(yǔ)句主要用 while 和 for 循環(huán)。
3.1 if 條件語(yǔ)句
Kotlin 中的 if 語(yǔ)句和 java 中 if 語(yǔ)句沒(méi)啥區(qū)別,以上述函數(shù)為例修改如下:
fun largerNum(num1: Int, num2: Int): Int {
var value = 0
if (num1 > num2) {
value = num1
} else {
value = num2
}
return value
}
不過(guò) Kotlin 中 if 語(yǔ)句可以有返回值,返回值是 if 語(yǔ)句每一個(gè)條件中最后一行代碼的返回值。上述函數(shù)可以簡(jiǎn)化如下:
fun largerNum(num1: Int, num2: Int): Int {
val value = if (num1 > num2) {
num1
} else {
num2
}
return value
}
將 if 語(yǔ)句直接返回,繼續(xù)簡(jiǎn)化:
fun largerNum(num1: Int, num2: Int): Int {
return if (num1 > num2) {
num1
} else {
num2
}
}
當(dāng)一個(gè)函數(shù)只有一行代碼時(shí),可以省略函數(shù)體部分,直接將這一行代碼使用等號(hào)串連在函數(shù)定義的尾部。上述函數(shù)和一行代碼的作用是相同的,從而可以進(jìn)一步精簡(jiǎn):
fun largerNum(num1: Int, num2: Int): Int = if (num1 > num2) {
num1
} else {
num2
}
當(dāng)然也可以直接壓縮成一行代碼:
fun largerNum(num1: Int, num2: Int): Int = if (num1 > num2) num1 else num2
3.2 when 條件語(yǔ)句
Kotlin 中的 when 語(yǔ)句有點(diǎn)類似于 java 中的 switch 語(yǔ)句,但強(qiáng)大得多。
用 if 語(yǔ)句實(shí)現(xiàn)個(gè) 輸入學(xué)生名字返回該學(xué)生的分?jǐn)?shù) 的函數(shù)如下:
fun getScore(name: String) = if (name == "Wonderful") {
100
} else if (name == "Tome") {
86
} else if (name == "Jack") {
60
} else {
0
}
when 語(yǔ)句允許傳入一個(gè)任意類型的參數(shù),然后可以在 when 的結(jié)構(gòu)體中定義一系列的條件,格式是:
匹配值 -> { 執(zhí)行邏輯 }
當(dāng)執(zhí)行邏輯只有一行代碼時(shí), { } 可以省略。
用 when 語(yǔ)句實(shí)現(xiàn)上述方法如下:
fun getScore(name: String) = when (name) {
"Wonderful" -> 100
"Tome" -> 86
"Jack" -> 60
else -> 0
}
在某些場(chǎng)景,比如 所有名字以Won開(kāi)頭的學(xué)生分?jǐn)?shù)都是100分,則上述函數(shù)可以用不帶參數(shù)的 when 語(yǔ)句實(shí)現(xiàn):
fun getScore(name: String) = when {
name.startsWith("Won") -> 100
name == "Tome" -> 86
name == "Jack" -> 60
else -> 0
}
注:when語(yǔ)句不帶參數(shù)的用法不太常用
除此之外,when 語(yǔ)句還可以進(jìn)行類型匹配,如:
fun checkNumber(num: Number) {
when (num) {
is Int -> print("整數(shù)") // is 關(guān)鍵字相當(dāng)于 Java 中的 instanceof 關(guān)鍵字
is Double -> print("Double")
else -> print("number not support")
}
}
3.3 循環(huán)語(yǔ)句
Kotlin 中的 while 循環(huán)語(yǔ)句和在 Java 中的使用沒(méi)有區(qū)別,而 for 循環(huán)在 Kotlin 中做了很大幅度的修改。
Java 中常用的 for-i 循環(huán)在 Kotlin 中被舍棄了,Java 中的 for-each 循環(huán)在 Kotlin 中變成了 for-in 循環(huán)。
Kotlin 用 .. 創(chuàng)建閉區(qū)間,用 until 關(guān)鍵字創(chuàng)建左閉右開(kāi)的區(qū)間,如:
val range = 0..10 // 數(shù)學(xué)中的[0, 10]
val range = 0 until 10 // 數(shù)學(xué)中的[0, 10)
Kotlin 中 for 循環(huán)用法如下:
fun main() {
// 遍歷[0, 10]中的每一個(gè)元素
for (i in 0..10){
println(i)
}
// 遍歷[0, 10)的時(shí)候,每次循環(huán)會(huì)在區(qū)間范圍內(nèi)遞增2,相當(dāng)于 for-i 中的 i = i + 2 效果
// step 關(guān)鍵字可以跳過(guò)其中一些元素
for (i in 0 until 10 step 2){
println(i)
}
// 降序遍歷[0, 10]中的每一個(gè)元素
// downTo 關(guān)鍵字用來(lái)創(chuàng)建降序的空間
for (i in 10 downTo 1){
println(i)
}
}
4. 面向?qū)ο缶幊?/h1>
不同于面向過(guò)程的語(yǔ)言(如 C 語(yǔ)言),面向?qū)ο蟮恼Z(yǔ)言是可以創(chuàng)建類的。
類是對(duì)事物的一種封裝,而面向?qū)ο缶幊套罨镜乃枷刖褪峭ㄟ^(guò)這種類的封裝,在適當(dāng)?shù)臅r(shí)候創(chuàng)建該類的對(duì)象,然后調(diào)用對(duì)象中的字段和函數(shù)來(lái)滿足實(shí)際編程的需求。
建立在基本思想之上,面向?qū)ο缶幊踢€有其他特性如繼承、多態(tài)等。
4.1 類與對(duì)象
在 Kotlin 中,用 class 關(guān)鍵字來(lái)聲明一個(gè)類,比如創(chuàng)建一個(gè) Person 類如下:
// 定義一個(gè)Person類,包含name和age字段,一個(gè)eat() 函數(shù)
class Person {
var name = ""
var age = 0
fun eat(){
println("$name is eating. He is $age years old")
}
}
定義好類后,類的實(shí)例化方式和 Java 是基本類似的,但不需要 new 關(guān)鍵字,只需val p = Person(),如下:
fun main() {
val p = Person()
p.name = "Wonderful"
p.age = 18
p.eat()
}
4.2 繼承與構(gòu)造函數(shù)
現(xiàn)創(chuàng)建一個(gè) Student 類如下:
class Student {
var sno = "" // 學(xué)號(hào)
var grade = 0 // 年級(jí)
}
如果要讓 Student 類繼承 Person 類,需要做以下兩件事:
- 使
Person類可以被繼承(注:Kotlin 中任何一個(gè)非抽象類默認(rèn)是不可被繼承的),在Person類前面加上關(guān)鍵字open就可以了:
open class Person {
var name = ""
var age = 0
fun eat(){
println("$name is eating. He is $age years old")
}
}
- 讓
Student類繼承Person類,Kotlin 中統(tǒng)一用冒號(hào):繼承類或?qū)崿F(xiàn)接口,如下:
class Student : Person() {
var sno = "" // 學(xué)號(hào)
var grade = 0 // 年級(jí)
}
上面繼承代碼中 Person 類的后面要加一對(duì)(),表示 Student 類的主構(gòu)造函數(shù)在初始化時(shí)會(huì)調(diào)用 Person 類的無(wú)參構(gòu)造函數(shù),即使在無(wú)參情況下也不能取消括號(hào)。
Kotlin 的構(gòu)造函數(shù)有兩種:主構(gòu)造函數(shù) 和 次構(gòu)造函數(shù)
主構(gòu)造函數(shù) 沒(méi)有函數(shù)體,每個(gè)類默認(rèn)會(huì)有一個(gè)不帶參數(shù)的主構(gòu)造函數(shù),也可以在類名后面直接定義來(lái)顯式指明參數(shù),如:
class Student(val sno: String, val grade: Int) : Person() { }
這樣,實(shí)例化 Student 類時(shí)需要傳入構(gòu)造函數(shù)中的參數(shù):
val student = Student("no123", 6) // 學(xué)號(hào) no123,年級(jí) 6
如果想在實(shí)例化類時(shí)在主構(gòu)造函數(shù)中實(shí)現(xiàn)一些邏輯,則可以將邏輯寫(xiě)在 Kotlin 提供的 init 結(jié)構(gòu)體中:
class Student(val sno: String, val grade: Int) : Person() {
init {
// 實(shí)例化時(shí)打印學(xué)號(hào)和年級(jí)
println("sno is $sno")
println("grade is $grade")
}
}
子類的主構(gòu)造函數(shù)調(diào)用父類中的哪個(gè)構(gòu)造函數(shù),在繼承時(shí)通過(guò)括號(hào)來(lái)指定。
如果把 Person 類的姓名和年齡放到主構(gòu)造函數(shù)中,如下:
open class Person(val name: String, val age: Int) {
fun eat() {
println("$name is eating. He is $age years old")
}
}
此時(shí),Person 類已經(jīng)沒(méi)有無(wú)參構(gòu)造函數(shù)了,Student 類要繼承 Person 類也要在主構(gòu)造函數(shù)中加上姓名和年齡這兩個(gè)參數(shù),如下:
// 這邊增加的 name 和 age 字段不能聲明成 val,因?yàn)樵谥鳂?gòu)造函數(shù)中聲明 val 或 var 的參數(shù)會(huì)將自動(dòng)
// 成為該類的字段,這會(huì)導(dǎo)致和父類中同名的字段沖突
class Student(val sno: String, val grade: Int, name: String, age: Int) : Person(name, age) {
init {
println("sno is $sno")
println("grade is $grade")
}
}
任何一個(gè)類只能有一個(gè)主構(gòu)造函數(shù),但可以有多個(gè)次構(gòu)造函數(shù)。次構(gòu)造函數(shù) 也可用于實(shí)例化一個(gè)類,它是有函數(shù)體的。
Kotlin 規(guī)定,當(dāng)一個(gè)類既有主構(gòu)造函數(shù)也有次構(gòu)造函數(shù)時(shí),所有的次構(gòu)造函數(shù)都必須調(diào)用主構(gòu)造函數(shù)(包括間接調(diào)用)。
次構(gòu)造函數(shù) 是通過(guò) constructor 關(guān)鍵字來(lái)定義的,如定義 Student 類的次構(gòu)造函數(shù)如下:
class Student(val sno: String, val grade: Int, name: String, age: Int) : Person(name, age) {
init {
println("sno is $sno")
println("grade is $grade")
}
constructor(name: String, age: Int) : this("", 0, name, age){ }
constructor() : this("", 0){ }
}
此時(shí)就可以有 3 種方式來(lái)實(shí)例化 Student 類:
val student1 = Student()
val student2 = Student("Wonderful", 18)
val student3 = Student("no123", 6, "Wonderful", 18)
還有種特殊情況,類中只有次構(gòu)造函數(shù),沒(méi)有主構(gòu)造函數(shù)(當(dāng)一個(gè)類沒(méi)有顯式定義主構(gòu)造函數(shù)且定義了次構(gòu)造函數(shù)時(shí),它就是沒(méi)有主構(gòu)造函數(shù)的),此時(shí)繼承類時(shí)就不需要再加上括號(hào)了,如下:
class SpecialStudent : Person {
constructor(name: String, age: Int) : super(name, age) { }
}
4.3 接口
接口是用于實(shí)現(xiàn)多態(tài)編程的重要組成部分,Kotlin 和 Java 一樣也是一個(gè)類只能繼承一個(gè)父類,卻可以實(shí)現(xiàn)多個(gè)接口。
定義個(gè) Study 接口,接口中的函數(shù)不要求有函數(shù)體,如下:
interface Study {
fun readBooks()
fun doHomework()
}
在 Kotlin 中,統(tǒng)一用冒號(hào),中間用逗號(hào)分隔,來(lái)繼承類或?qū)崿F(xiàn)接口,如在 Student 類中實(shí)現(xiàn) Study 接口:
class Student(val sno: String, val grade: Int, name: String, age: Int) : Person(name, age), Study {
init {
println("sno is $sno")
println("grade is $grade")
}
override fun readBooks() {
println("$name is reading")
}
override fun doHomework() {
println("$name is doing homework")
}
constructor(name: String, age: Int) : this("", 0, name, age){ }
constructor() : this("", 0){ }
}
在 mian() 中調(diào)用這兩個(gè)接口函數(shù)如下:
fun main() {
val student = Student("no123", 6, "Wonderful", 18)
doStudy(student)
}
fun doStudy(study: Study){
study.readBooks()
study.doHomework()
}
上面由于 Student 類實(shí)現(xiàn)了 Study 接口,從而可以把 Student 類的實(shí)例傳遞給 doStudy 函數(shù),這種面向接口編程也可以稱為多態(tài)。
Kotlin 還允許對(duì)接口中定義的函數(shù)進(jìn)行默認(rèn)實(shí)現(xiàn),如:
// 當(dāng)一個(gè)類實(shí)現(xiàn) Sduty 接口時(shí),只會(huì)強(qiáng)制要求實(shí)現(xiàn) readBooks() 函數(shù),
// 而 doHomework() 函數(shù)可以自由選擇是否實(shí)現(xiàn)
interface Study {
fun readBooks()
fun doHomework() {
println("do homework default implementation")
}
}
Kotlin 和 Java 中函數(shù)的可見(jiàn)性修飾符比較如下:

4.4 數(shù)據(jù)類與單例類
在 Java 中數(shù)據(jù)類通常需要重寫(xiě) equals()、hashCode()、toString() 幾個(gè)方法,如用 Java 構(gòu)建一個(gè)手機(jī)數(shù)據(jù)類如下:
public class CellPhone {
String brand; // 品牌
double price; // 價(jià)格
public CellPhone(String brand, double price) {
this.brand = brand;
this.price = price;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CellPhone cellPhone = (CellPhone) o;
return Double.compare(cellPhone.price, price) == 0 &&
brand.equals(cellPhone.brand);
}
@Override
public int hashCode() {
return brand.hashCode() + (int) price;
}
@Override
public String toString() {
return "CellPhone{" +
"brand='" + brand + '\'' +
", price=" + price +
'}';
}
}
如果用 Kotlin 只需在數(shù)據(jù)類前面聲明關(guān)鍵字 data 就可以了,如下:
data class CellPhone(val brand: String, val price: Double)
在 Java 中常見(jiàn)的單例模式寫(xiě)法如下:
public class Singleton {
private static Singleton instance;
private Singleton() { }
public synchronized static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
如果用 Kotlin 只需把 class 關(guān)鍵字改成 object 就可以了,如下:
object Singleton { }
5. Lambda 編程
5.1 集合的創(chuàng)建與遍歷
一般的集合主要就是 List、Set 和 Map,List 的主要實(shí)現(xiàn)類是 ArrayList 和 LinkedList,Set 的主要實(shí)現(xiàn)類是 HashSet,Map 的主要實(shí)現(xiàn)類是 HashMap。
創(chuàng)建一個(gè)包含許多水果名稱的集合,傳統(tǒng)的寫(xiě)法如下:
val list = ArrayList<String>()
list.add("apple")
list.add("orange")
list.add("pear")
上面這種方式比較繁瑣,Kotlin 專門(mén)提供了一個(gè)內(nèi)置的 listOf() 函數(shù)來(lái)簡(jiǎn)化初始化集合的寫(xiě)法,如下:
val list = listOf("apple", "orange", "pear")
不過(guò) listOf() 函數(shù)創(chuàng)建的是一個(gè)不可變集合,創(chuàng)建可變集合用 mutableListOf() 函數(shù)。
用 Set 集合也差不多,將創(chuàng)建集合的方式變成 setOf() 和 mutableSetOf() 函數(shù)而已。
注:和 List 集合不同的是,Set 集合底層使用 hash 映射機(jī)制來(lái)存放數(shù)據(jù),因而集合中的元素?zé)o法保證有序。
用 Map 集合創(chuàng)建一個(gè)包含許多水果名稱和對(duì)應(yīng)編號(hào)的集合,傳統(tǒng)的寫(xiě)法如下:
val map = HashMap<String, Int>()
map.put("apple", 1)
map.put("orange", 2)
map.put("pear", 3)
但在 Kotlin 中不建議用 put() 和 get() 方法來(lái)對(duì) Map 進(jìn)行數(shù)據(jù)操作,而推薦使用一種類似于數(shù)組下標(biāo)的語(yǔ)法結(jié)構(gòu),如添加 map["apple] = 1,讀取 val number = map["apple"],因此上面代碼可改為:
val map = HashMap<String, Int>()
map["apple"] = 1
map["orange"] = 2
map["pear"] = 3
或者使用 mapOf() 和 mutableMapOf() 來(lái)簡(jiǎn)化:
val map = mapOf("apple" to 1, "orange" to 2, "pear" to 3)
5.2 集合的函數(shù)式 API
要在一個(gè)水果集合里找到單詞最長(zhǎng)的那個(gè)水果,可以用如下代碼實(shí)現(xiàn):
val list = listOf("apple", "orange", "pear")
var maxLengthFruit = ""
for (fruit in list){
if (fruit.length > maxLengthFruit.length){
maxLengthFruit = fruit
}
}
println("max length fruit is $maxLengthFruit")
但如果使用集合的函數(shù)式 API,就可以簡(jiǎn)化為:
val list = listOf("apple", "orange", "pear")
val maxLengthFruit = list.maxBy { it.length }
println("max length fruit is $maxLengthFruit")
上面代碼使用了 Lambda 表達(dá)式的語(yǔ)法結(jié)構(gòu),只需一行代碼就能找到集合中單詞最長(zhǎng)的水果。
Lambda 就是一小段可以作為參數(shù)傳遞的代碼,它的語(yǔ)法結(jié)構(gòu)如下:
{ 參數(shù)名1:參數(shù)類型,參數(shù)名2:參數(shù)類型 -> 函數(shù)體 }
最外層是一對(duì)大括號(hào),若有參數(shù)傳入到 Lambda 表達(dá)式,需要聲明參數(shù)列表,參數(shù)列表結(jié)尾用符號(hào) -> 表示參數(shù)列表的結(jié)束以及函數(shù)體的開(kāi)始,函數(shù)體中可以編寫(xiě)任意行代碼,并且最后一行代碼會(huì)自動(dòng)作為返回值。
當(dāng)然,多數(shù)情況下我們寫(xiě)的更多的是簡(jiǎn)化的寫(xiě)法,以上面例子為例,maxby 就是一個(gè)普通的函數(shù),接收了一個(gè) Lambda 類型的參數(shù),若剛開(kāi)始套用 Lambda 表達(dá)式的語(yǔ)法結(jié)構(gòu),可變成如下:
val list = listOf("apple", "orange", "pear")
val lambda = { fruit: String -> fruit.length }
val maxLengthFruit = list.maxBy(lambda) // maxBy 函數(shù)實(shí)質(zhì)上是接收了一個(gè) Lambda 參數(shù)
由于可以直接將 lambda 表達(dá)式傳入 maxBy 函數(shù)中,因此可簡(jiǎn)化為:
val maxLengthFruit = list.maxBy({ fruit: String -> fruit.length })
Kotlin 規(guī)定,當(dāng) Lambda 參數(shù)是函數(shù)的最后一個(gè)參數(shù)時(shí),可將 Lambda 表達(dá)式移到函數(shù)括號(hào)外面,如下:
val maxLengthFruit = list.maxBy() { fruit: String -> fruit.length }
如果 Lambda 參數(shù)是函數(shù)的唯一一個(gè)參數(shù)的話,可將函數(shù)的括號(hào)省略:
val maxLengthFruit = list.maxBy { fruit: String -> fruit.length }
由于 Kotlin 擁有類型推導(dǎo)機(jī)制,Lambda 表達(dá)式中的參數(shù)列表大多數(shù)情況下可不必聲明參數(shù)類型,從而進(jìn)一步簡(jiǎn)化為:
val maxLengthFruit = list.maxBy { fruit -> fruit.length }
最后,當(dāng) Lambda 表達(dá)式的參數(shù)列表只有一個(gè)參數(shù)時(shí),也不必聲明參數(shù)名,可用 it 關(guān)鍵字代替:
val maxLengthFruit = list.maxBy { it.length }
接下來(lái)介紹幾個(gè)集合中比較常用的函數(shù)式 API:
- map 函數(shù)
集合中的 map 函數(shù)用于將集合中的每個(gè)元素都映射成一個(gè)另外的值,映射的規(guī)則在 Lambda 表達(dá)式中指定,最終生成一個(gè)新的集合。
如把所有水果名變成大寫(xiě):
val list = listOf("apple", "orange", "pear")
val newList = list.map { it.toUpperCase(Locale.ROOT) } // 新的列表水果名都是大寫(xiě)的
- filter 函數(shù)
filter 函數(shù)是用來(lái)過(guò)濾集合中的數(shù)據(jù)的,可單獨(dú)使用,也可配合 map 一起使用。
如只保留 5 個(gè)字母以內(nèi)的水果且所有水果名大寫(xiě):
val list = listOf("apple", "orange", "pear")
val newList = list.filter { it.length <= 5 }.map { it.toUpperCase(Locale.ROOT) }
注:上面若改成先調(diào)用 map 再調(diào)用 filter 函數(shù),效率會(huì)差很多,因?yàn)檫@相當(dāng)于對(duì)集合的所有元素進(jìn)行一次映射轉(zhuǎn)換后再過(guò)濾。
- any 和 all 函數(shù)
any 函數(shù)用于判斷集合中是否至少存在一個(gè)元素滿足指定條件。
all 函數(shù)用于判斷集合中是否所有元素都滿足指定條件。
用法如下:
val list = listOf("apple", "orange", "pear")
val anyResult = list.any { it.length <= 5 } // 集合中是否存在5個(gè)字母以內(nèi)的單詞,返回 true
val allResult = list.all { it.length <= 5 } // 集合中是否所有單詞都在5個(gè)字母內(nèi),返回 false
println("anyResult is $anyResult , allResult is $allResult")
5.3 Java 函數(shù)式 API 使用
在 Kotlin 代碼中調(diào)用 Java 方法,若該方法接收一個(gè) Java 單抽象方法接口(接口中只有一個(gè)待實(shí)現(xiàn)方法)參數(shù),就可以使用函數(shù)式 API。
如 Java 原生 API 中的 Runnable 接口就是一個(gè)單抽象方法接口:
public interface Runnable {
// 這個(gè)接口中只有一個(gè)待實(shí)現(xiàn)的 run() 方法
void run();
}
以 Java 的線程類 Thread 為例,Thread 類的構(gòu)造方法中接收一個(gè) Runnable 參數(shù),Java 代碼創(chuàng)建并執(zhí)行一個(gè)子線程:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Thread is running");
}
}).start();
上面代碼用 Kotlin 實(shí)現(xiàn)如下:
Thread(object : Runnable {
override fun run() {
println("Thread is running")
}
}).start()
上面 Thread 類的構(gòu)造方法是符合 Java 函數(shù)式 API 使用條件的,因此可簡(jiǎn)化為:
Thread(Runnable { println("Thread is running") }).start()
若一個(gè) Java 方法的參數(shù)列表只有唯一一個(gè)單抽象方法接口參數(shù),可把接口名省略:
Thread({ println("Thread is running") }).start()
當(dāng) Lambda 表達(dá)式是方法的最后一個(gè)參數(shù)時(shí),可把它移到方法括號(hào)外面,同時(shí)如果它還是方法的唯一一個(gè)參數(shù),可把方法的括號(hào)省略:
Thread { println("Thread is running") }.start()
注:以上 Java 函數(shù)式 API 的使用都限定與從 Kotlin 中調(diào)用 Java 方法,并且單抽象方法接口也必須是 Java 語(yǔ)言定義的。
6. 空指針檢查
先看一段簡(jiǎn)單的 Java 代碼:
public void doStudy(Study study){
study.readBooks();
study.doHomework();
}
若向 doStudy() 方法傳入一個(gè) null 參數(shù),那么上面代碼就會(huì)報(bào)空指針異常,更加穩(wěn)妥的做法是做判空處理:
public void doStudy(Study study){
if (study != null){
study.readBooks();
study.doHomework();
}
}
若用 Kotlin 實(shí)現(xiàn)上面 doStudy() 函數(shù),如下:
fun doStudy(study : Study){
study.readBooks();
study.doHomework();
}
它和 Java 版本沒(méi)啥區(qū)別,但它是沒(méi)有空指針風(fēng)險(xiǎn)的,因?yàn)?Kotlin 默認(rèn)所有參數(shù)和變量都不可空,當(dāng)你傳一個(gè) null 參數(shù)時(shí),編譯器會(huì)提示錯(cuò)誤:

Kotlin 把空指針異常的檢查提前到了編譯時(shí)期,程序若存在空指針異常的風(fēng)險(xiǎn),那么在編譯時(shí)會(huì)直接報(bào)錯(cuò)。
如果希望傳入的參數(shù)可為空,Kotlin 中在類名后面加一個(gè)問(wèn)號(hào)就可以了,比如 Int 表示不可為空的整型,而 Int? 就表示可為空的整形。
把上面代碼中參數(shù)的類型由 Study 變?yōu)?Study?,如下:

發(fā)現(xiàn)調(diào)用 doStudy() 函數(shù)時(shí)可以傳入 null 參數(shù)了,但調(diào)用參數(shù)的兩個(gè)方法時(shí),會(huì)出現(xiàn)紅色的錯(cuò)誤提示,這是因?yàn)榘褏?shù)改成了可空的 Study? 類型,此時(shí)調(diào)用參數(shù)的 readBooks() 和 doHomework() 方法可能造成空指針異常。
還需要做個(gè)判空處理,就不會(huì)出現(xiàn)錯(cuò)誤了:
fun doStudy(study: Study?) {
if (study != null) {
study.readBooks()
study.doHomework()
}
}
當(dāng)然,用一些判空輔助工具會(huì)更加簡(jiǎn)單進(jìn)行判空處理,下面介紹幾個(gè) Kotlin 的判空輔助工具:
- 操作符
?.
操作符 ?. 的作用是當(dāng)對(duì)象不為空時(shí)正常調(diào)用相應(yīng)的方法,為空時(shí)則什么都不做。
用操作符 ?. 上述代碼可改為:
fun doStudy(study: Study?) {
study?.readBooks()
study?.doHomework()
}
- 操作符
?:
操作符 ?: 的左右兩邊都接收一個(gè)表達(dá)式,若左邊表達(dá)式的結(jié)果不為空則返回左邊表達(dá)式的結(jié)果,否則返回右邊表達(dá)式的結(jié)果。
比如把如下代碼:
val c = if (a != null) {
a
} else {
b
}
用操作符 ?: 就可簡(jiǎn)化為:
val c = a ?: b
- 操作符
!!
操作符 !! 的作用是告訴 Kotlin 非常確信對(duì)象不會(huì)為空,不需要 Kotlin 幫忙做空指針檢查,若出現(xiàn)問(wèn)題再直接拋出空指針異常。
比如以下代碼:

上面代碼中 printUpperCase() 函數(shù)并不知道外部已經(jīng)對(duì) content 進(jìn)行了非空檢查,在調(diào)用 toUpperCase() 方法時(shí),還認(rèn)為存在空指針風(fēng)險(xiǎn),從而編譯不通過(guò)。這種情況想要強(qiáng)制通過(guò)編譯,可在對(duì)象的后面加上!!,如下:
fun printUpperCase(){
val upperCase = content!!.toUpperCase(Locale.ROOT)
println(upperCase)
}
let函數(shù)
let 函數(shù)提供了函數(shù)式 API 的編程接口,并將原始調(diào)用對(duì)象作為參數(shù)傳遞到 Lambda 表達(dá)式中,如下:
obj.let { obj2 -> // 這里的 obj2 和 obj 是同一個(gè)對(duì)象
// 編寫(xiě)具體的業(yè)務(wù)邏輯
}
let 函數(shù)屬于 Kotlin 中的標(biāo)準(zhǔn)函數(shù),可以處理全局變量的判空問(wèn)題(if 判斷語(yǔ)句無(wú)法做到這一點(diǎn)),它配合操作符 ?. 可以在做空指針檢查時(shí)起到很大作用。
如上述的 doStudy() 函數(shù)代碼可用 let 函數(shù)進(jìn)行優(yōu)化,如下:
fun doStudy(study: Study?) {
// study 對(duì)象不為空時(shí)就調(diào)用 let 函數(shù),let 函數(shù)會(huì)將 study 對(duì)象本身作為參數(shù)傳遞到 Lambda 表達(dá)式中
study?.let { stu ->
stu.readBooks()
stu.doHomework()
}
}
當(dāng) Lambda 表達(dá)式的參數(shù)列表只有一個(gè)參數(shù)時(shí),可不聲明參數(shù)名,用 it 關(guān)鍵字代替即可,從而可簡(jiǎn)化為:
fun doStudy(study: Study?) {
study?.let {
it.readBooks()
it.doHomework()
}
}
7. Kotlin 中的小技巧
7.1 字符串內(nèi)嵌表達(dá)式
Kotlin 允許在字符串里嵌入 ${} 這種語(yǔ)法結(jié)構(gòu)的表達(dá)式,并在運(yùn)行時(shí)使用表達(dá)式執(zhí)行的結(jié)果替代這一部分內(nèi)容,大大提升了易讀性和易用性:
"hello, ${obj.name}, nice to meet you"
當(dāng)表達(dá)式僅有一個(gè)變量時(shí),可將兩邊的大括號(hào)省略:
"hello, $name, nice to meet you"
舉個(gè)例子:
val name = "Wonderful"
val age = 18
println("My name is " + name + ", " + age + "years old")
用字符串內(nèi)嵌表達(dá)式的寫(xiě)法可簡(jiǎn)化為:
val name = "Wonderful"
val age = 18
println("My name is $name, $age years old")
7.2 函數(shù)的參數(shù)默認(rèn)值
Kotlin 中,定義函數(shù)時(shí)給任意參數(shù)設(shè)定一個(gè)默認(rèn)值,調(diào)用時(shí)就不會(huì)強(qiáng)制為此參數(shù)傳值,在此參數(shù)沒(méi)傳值的情況下使用設(shè)定的默認(rèn)值。如:
// 這里給第二個(gè)參數(shù) str 設(shè)定了個(gè)默認(rèn)值 “hello”
fun printParams(num: Int, str: String = "hello"){
println("num is $num , str is $str")
}
這樣調(diào)用時(shí)可不用給第二個(gè)參數(shù)傳值,如printParams(123)。但如果改成給第一個(gè)參數(shù)設(shè)定默認(rèn)值的話:
// 這里給第一個(gè)參數(shù) num 設(shè)定了個(gè)默認(rèn)值 100
fun printParams(num: Int = 100, str: String){
println("num is $num , str is $str")
}
此時(shí)再調(diào)用諸如 printParams("world") 就會(huì)報(bào)類型匹配錯(cuò)誤了,這時(shí)需要通過(guò)鍵值對(duì)的方式來(lái)傳參,從而不必按照參數(shù)定義的順序來(lái)傳參,如 printParams(str = "world") 。
給函數(shù)設(shè)定參數(shù)默認(rèn)值這個(gè)功能,使得主構(gòu)造函數(shù)很大程度上替代了次構(gòu)造函數(shù),從而次構(gòu)造函數(shù)比較少使用到。
本篇文章就介紹到這。