本篇文章主要介紹以下幾個知識點:
- Kotlin 語言簡介
- 變量和函數(shù)
- 程序的邏輯控制
- 面向?qū)ο缶幊?/li>
- Lambda 編程
- 空指針檢查
- Kotlin 小技巧
內(nèi)容參考自第一行代碼第3版
1. Kotlin 語言簡介
編程語言大致可分為兩類:編譯型語言和解釋型語言。
編譯型:編譯器將編寫的源代碼一次性地編譯成計算機可識別的二進制文件填大,然后計算機直接執(zhí)行解孙,如 C、C++亡容。
解釋型:程序運行時忍疾,解釋器會一行行讀取編寫的源代碼沸毁,然后實時地將這些源代碼解釋成計算機可識別的二進制數(shù)據(jù)后再執(zhí)行,如 Python亥鬓、JavaScript(解釋型語言效率會差些)完沪。
Java 先編譯再運行,但 Java 代碼編譯之后生成的是 class 文件贮竟,只有 Java 虛擬機才能識別丽焊,Java 虛擬機將編譯后的 class 文件解釋成二進制數(shù)據(jù)后再執(zhí)行较剃,因而 Java 屬于解釋型語言。
Kotlin 也是通過編譯器編譯成 class 文件技健,從而 Java 虛擬機可以識別写穴。
2. 變量和函數(shù)
2.1 變量
Kotlin 定義一個變量,只允許在變量前聲明兩種關(guān)鍵字:val
和 var
雌贱。
val (value的簡寫)聲明不可變的變量啊送,對應(yīng) java 中的 final
變量。
var (variable的簡寫)聲明可變的變量欣孤,對應(yīng) java 中的非 final
變量馋没。
使用技巧:編程時優(yōu)先使用 val
來聲明變量,當(dāng) va
l無法滿足需求時再使用 var
降传。
2.2 函數(shù)
fun (function的簡寫) 是定義函數(shù)的關(guān)鍵字
如定義個 返回兩個數(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í)行語句主要分 3 種:順序篷朵、條件和循環(huán)語句。
kotlin 中的條件語句主要用 if
和 when
語句婆排,循環(huán)語句主要用 while
和 for
循環(huán)声旺。
3.1 if 條件語句
Kotlin 中的 if
語句和 java 中 if
語句沒啥區(qū)別,以上述函數(shù)為例修改如下:
fun largerNum(num1: Int, num2: Int): Int {
var value = 0
if (num1 > num2) {
value = num1
} else {
value = num2
}
return value
}
不過 Kotlin 中 if
語句可以有返回值段只,返回值是 if
語句每一個條件中最后一行代碼的返回值腮猖。上述函數(shù)可以簡化如下:
fun largerNum(num1: Int, num2: Int): Int {
val value = if (num1 > num2) {
num1
} else {
num2
}
return value
}
將 if
語句直接返回,繼續(xù)簡化:
fun largerNum(num1: Int, num2: Int): Int {
return if (num1 > num2) {
num1
} else {
num2
}
}
當(dāng)一個函數(shù)只有一行代碼時赞枕,可以省略函數(shù)體部分澈缺,直接將這一行代碼使用等號串連在函數(shù)定義的尾部。上述函數(shù)和一行代碼的作用是相同的炕婶,從而可以進一步精簡:
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 條件語句
Kotlin 中的 when
語句有點類似于 java 中的 switch
語句姐赡,但強大得多。
用 if
語句實現(xiàn)個 輸入學(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 語句允許傳入一個任意類型的參數(shù)柠掂,然后可以在 when
的結(jié)構(gòu)體中定義一系列的條件雏吭,格式是:
匹配值 -> { 執(zhí)行邏輯 }
當(dāng)執(zhí)行邏輯只有一行代碼時, { } 可以省略陪踩。
用 when
語句實現(xiàn)上述方法如下:
fun getScore(name: String) = when (name) {
"Wonderful" -> 100
"Tome" -> 86
"Jack" -> 60
else -> 0
}
在某些場景,比如 所有名字以Won開頭的學(xué)生分?jǐn)?shù)都是100分悉抵,則上述函數(shù)可以用不帶參數(shù)的 when
語句實現(xiàn):
fun getScore(name: String) = when {
name.startsWith("Won") -> 100
name == "Tome" -> 86
name == "Jack" -> 60
else -> 0
}
注:when語句不帶參數(shù)的用法不太常用
除此之外肩狂,when
語句還可以進行類型匹配,如:
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)語句
Kotlin 中的 while
循環(huán)語句和在 Java 中的使用沒有區(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)建左閉右開的區(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]中的每一個元素
for (i in 0..10){
println(i)
}
// 遍歷[0, 10)的時候,每次循環(huán)會在區(qū)間范圍內(nèi)遞增2态蒂,相當(dāng)于 for-i 中的 i = i + 2 效果
// step 關(guān)鍵字可以跳過其中一些元素
for (i in 0 until 10 step 2){
println(i)
}
// 降序遍歷[0, 10]中的每一個元素
// downTo 關(guān)鍵字用來創(chuàng)建降序的空間
for (i in 10 downTo 1){
println(i)
}
}
4. 面向?qū)ο缶幊?/h1>
不同于面向過程的語言(如 C 語言)杭措,面向?qū)ο蟮恼Z言是可以創(chuàng)建類的。
類是對事物的一種封裝钾恢,而面向?qū)ο缶幊套罨镜乃枷刖褪峭ㄟ^這種類的封裝手素,在適當(dāng)?shù)臅r候創(chuàng)建該類的對象,然后調(diào)用對象中的字段和函數(shù)來滿足實際編程的需求瘩蚪。
建立在基本思想之上泉懦,面向?qū)ο缶幊踢€有其他特性如繼承、多態(tài)等疹瘦。
4.1 類與對象
在 Kotlin 中崩哩,用 class 關(guān)鍵字來聲明一個類,比如創(chuàng)建一個 Person
類如下:
// 定義一個Person類言沐,包含name和age字段邓嘹,一個eat() 函數(shù)
class Person {
var name = ""
var age = 0
fun eat(){
println("$name is eating. He is $age years old")
}
}
定義好類后,類的實例化方式和 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)建一個 Student
類如下:
class Student {
var sno = "" // 學(xué)號
var grade = 0 // 年級
}
如果要讓 Student
類繼承 Person
類鸯乃,需要做以下兩件事:
- 使
Person
類可以被繼承(注:Kotlin 中任何一個非抽象類默認(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)一用冒號:
繼承類或?qū)崿F(xiàn)接口缨睡,如下:
class Student : Person() {
var sno = "" // 學(xué)號
var grade = 0 // 年級
}
上面繼承代碼中 Person
類的后面要加一對()鸟悴,表示 Student
類的主構(gòu)造函數(shù)在初始化時會調(diào)用 Person
類的無參構(gòu)造函數(shù),即使在無參情況下也不能取消括號奖年。
Kotlin 的構(gòu)造函數(shù)有兩種:主構(gòu)造函數(shù) 和 次構(gòu)造函數(shù)
主構(gòu)造函數(shù) 沒有函數(shù)體细诸,每個類默認(rèn)會有一個不帶參數(shù)的主構(gòu)造函數(shù),也可以在類名后面直接定義來顯式指明參數(shù)陋守,如:
class Student(val sno: String, val grade: Int) : Person() { }
這樣震贵,實例化 Student 類時需要傳入構(gòu)造函數(shù)中的參數(shù):
val student = Student("no123", 6) // 學(xué)號 no123,年級 6
如果想在實例化類時在主構(gòu)造函數(shù)中實現(xiàn)一些邏輯水评,則可以將邏輯寫在 Kotlin 提供的 init
結(jié)構(gòu)體中:
class Student(val sno: String, val grade: Int) : Person() {
init {
// 實例化時打印學(xué)號和年級
println("sno is $sno")
println("grade is $grade")
}
}
子類的主構(gòu)造函數(shù)調(diào)用父類中的哪個構(gòu)造函數(shù)猩系,在繼承時通過括號來指定。
如果把 Person
類的姓名和年齡放到主構(gòu)造函數(shù)中中燥,如下:
open class Person(val name: String, val age: Int) {
fun eat() {
println("$name is eating. He is $age years old")
}
}
此時寇甸,Person
類已經(jīng)沒有無參構(gòu)造函數(shù)了,Student
類要繼承 Person
類也要在主構(gòu)造函數(shù)中加上姓名和年齡這兩個參數(shù),如下:
// 這邊增加的 name 和 age 字段不能聲明成 val拿霉,因為在主構(gòu)造函數(shù)中聲明 val 或 var 的參數(shù)會將自動
// 成為該類的字段吟秩,這會導(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òu)造函數(shù),但可以有多個次構(gòu)造函數(shù)绽淘。次構(gòu)造函數(shù) 也可用于實例化一個類涵防,它是有函數(shù)體的。
Kotlin 規(guī)定收恢,當(dāng)一個類既有主構(gòu)造函數(shù)也有次構(gòu)造函數(shù)時武学,所有的次構(gòu)造函數(shù)都必須調(diào)用主構(gòu)造函數(shù)(包括間接調(diào)用)。
次構(gòu)造函數(shù) 是通過 constructor
關(guān)鍵字來定義的伦意,如定義 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){ }
}
此時就可以有 3 種方式來實例化 Student
類:
val student1 = Student()
val student2 = Student("Wonderful", 18)
val student3 = Student("no123", 6, "Wonderful", 18)
還有種特殊情況火窒,類中只有次構(gòu)造函數(shù),沒有主構(gòu)造函數(shù)(當(dāng)一個類沒有顯式定義主構(gòu)造函數(shù)且定義了次構(gòu)造函數(shù)時驮肉,它就是沒有主構(gòu)造函數(shù)的)熏矿,此時繼承類時就不需要再加上括號了,如下:
class SpecialStudent : Person {
constructor(name: String, age: Int) : super(name, age) { }
}
4.3 接口
接口是用于實現(xiàn)多態(tài)編程的重要組成部分离钝,Kotlin 和 Java 一樣也是一個類只能繼承一個父類票编,卻可以實現(xiàn)多個接口。
定義個 Study
接口卵渴,接口中的函數(shù)不要求有函數(shù)體慧域,如下:
interface Study {
fun readBooks()
fun doHomework()
}
在 Kotlin 中,統(tǒng)一用冒號浪读,中間用逗號分隔昔榴,來繼承類或?qū)崿F(xiàn)接口,如在 Student 類中實現(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)用這兩個接口函數(shù)如下:
fun main() {
val student = Student("no123", 6, "Wonderful", 18)
doStudy(student)
}
fun doStudy(study: Study){
study.readBooks()
study.doHomework()
}
上面由于 Student
類實現(xiàn)了 Study
接口碘橘,從而可以把 Student
類的實例傳遞給 doStudy
函數(shù)互订,這種面向接口編程也可以稱為多態(tài)。
Kotlin 還允許對接口中定義的函數(shù)進行默認(rèn)實現(xiàn)痘拆,如:
// 當(dāng)一個類實現(xiàn) Sduty 接口時仰禽,只會強制要求實現(xiàn) readBooks() 函數(shù),
// 而 doHomework() 函數(shù)可以自由選擇是否實現(xiàn)
interface Study {
fun readBooks()
fun doHomework() {
println("do homework default implementation")
}
}
Kotlin 和 Java 中函數(shù)的可見性修飾符比較如下:
4.4 數(shù)據(jù)類與單例類
在 Java 中數(shù)據(jù)類通常需要重寫 equals()
纺蛆、hashCode()
吐葵、toString()
幾個方法,如用 Java 構(gòu)建一個手機數(shù)據(jù)類如下:
public class CellPhone {
String brand; // 品牌
double price; // 價格
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 中常見的單例模式寫法如下:
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
的主要實現(xiàn)類是 ArrayList
和 LinkedList
,Set
的主要實現(xiàn)類是 HashSet
,Map
的主要實現(xiàn)類是 HashMap
祥款。
創(chuàng)建一個包含許多水果名稱的集合清笨,傳統(tǒng)的寫法如下:
val list = ArrayList<String>()
list.add("apple")
list.add("orange")
list.add("pear")
上面這種方式比較繁瑣,Kotlin 專門提供了一個內(nèi)置的 listOf()
函數(shù)來簡化初始化集合的寫法刃跛,如下:
val list = listOf("apple", "orange", "pear")
不過 listOf()
函數(shù)創(chuàng)建的是一個不可變集合抠艾,創(chuàng)建可變集合用 mutableListOf()
函數(shù)。
用 Set
集合也差不多桨昙,將創(chuàng)建集合的方式變成 setOf()
和 mutableSetOf()
函數(shù)而已检号。
注:和 List
集合不同的是,Set
集合底層使用 hash
映射機制來存放數(shù)據(jù)蛙酪,因而集合中的元素?zé)o法保證有序齐苛。
用 Map
集合創(chuàng)建一個包含許多水果名稱和對應(yīng)編號的集合,傳統(tǒng)的寫法如下:
val map = HashMap<String, Int>()
map.put("apple", 1)
map.put("orange", 2)
map.put("pear", 3)
但在 Kotlin 中不建議用 put()
和 get()
方法來對 Map 進行數(shù)據(jù)操作桂塞,而推薦使用一種類似于數(shù)組下標(biāo)的語法結(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()
來簡化:
val map = mapOf("apple" to 1, "orange" to 2, "pear" to 3)
5.2 集合的函數(shù)式 API
要在一個水果集合里找到單詞最長的那個水果玛痊,可以用如下代碼實現(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,就可以簡化為:
val list = listOf("apple", "orange", "pear")
val maxLengthFruit = list.maxBy { it.length }
println("max length fruit is $maxLengthFruit")
上面代碼使用了 Lambda 表達(dá)式的語法結(jié)構(gòu)狂打,只需一行代碼就能找到集合中單詞最長的水果擂煞。
Lambda 就是一小段可以作為參數(shù)傳遞的代碼,它的語法結(jié)構(gòu)如下:
{ 參數(shù)名1:參數(shù)類型趴乡,參數(shù)名2:參數(shù)類型 -> 函數(shù)體 }
最外層是一對大括號对省,若有參數(shù)傳入到 Lambda 表達(dá)式,需要聲明參數(shù)列表浙宜,參數(shù)列表結(jié)尾用符號 ->
表示參數(shù)列表的結(jié)束以及函數(shù)體的開始官辽,函數(shù)體中可以編寫任意行代碼,并且最后一行代碼會自動作為返回值粟瞬。
當(dāng)然同仆,多數(shù)情況下我們寫的更多的是簡化的寫法,以上面例子為例裙品,maxby
就是一個普通的函數(shù)俗批,接收了一個 Lambda 類型的參數(shù),若剛開始套用 Lambda 表達(dá)式的語法結(jié)構(gòu)市怎,可變成如下:
val list = listOf("apple", "orange", "pear")
val lambda = { fruit: String -> fruit.length }
val maxLengthFruit = list.maxBy(lambda) // maxBy 函數(shù)實質(zhì)上是接收了一個 Lambda 參數(shù)
由于可以直接將 lambda 表達(dá)式傳入 maxBy
函數(shù)中岁忘,因此可簡化為:
val maxLengthFruit = list.maxBy({ fruit: String -> fruit.length })
Kotlin 規(guī)定,當(dāng) Lambda 參數(shù)是函數(shù)的最后一個參數(shù)時区匠,可將 Lambda 表達(dá)式移到函數(shù)括號外面干像,如下:
val maxLengthFruit = list.maxBy() { fruit: String -> fruit.length }
如果 Lambda 參數(shù)是函數(shù)的唯一一個參數(shù)的話帅腌,可將函數(shù)的括號省略:
val maxLengthFruit = list.maxBy { fruit: String -> fruit.length }
由于 Kotlin 擁有類型推導(dǎo)機制,Lambda 表達(dá)式中的參數(shù)列表大多數(shù)情況下可不必聲明參數(shù)類型麻汰,從而進一步簡化為:
val maxLengthFruit = list.maxBy { fruit -> fruit.length }
最后速客,當(dāng) Lambda 表達(dá)式的參數(shù)列表只有一個參數(shù)時,也不必聲明參數(shù)名五鲫,可用 it
關(guān)鍵字代替:
val maxLengthFruit = list.maxBy { it.length }
接下來介紹幾個集合中比較常用的函數(shù)式 API:
- map 函數(shù)
集合中的 map
函數(shù)用于將集合中的每個元素都映射成一個另外的值溺职,映射的規(guī)則在 Lambda 表達(dá)式中指定,最終生成一個新的集合位喂。
如把所有水果名變成大寫:
val list = listOf("apple", "orange", "pear")
val newList = list.map { it.toUpperCase(Locale.ROOT) } // 新的列表水果名都是大寫的
- filter 函數(shù)
filter 函數(shù)是用來過濾集合中的數(shù)據(jù)的浪耘,可單獨使用,也可配合 map
一起使用塑崖。
如只保留 5 個字母以內(nèi)的水果且所有水果名大寫:
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ù)七冲,效率會差很多,因為這相當(dāng)于對集合的所有元素進行一次映射轉(zhuǎn)換后再過濾弃舒。
- any 和 all 函數(shù)
any 函數(shù)用于判斷集合中是否至少存在一個元素滿足指定條件癞埠。
all 函數(shù)用于判斷集合中是否所有元素都滿足指定條件。
用法如下:
val list = listOf("apple", "orange", "pear")
val anyResult = list.any { it.length <= 5 } // 集合中是否存在5個字母以內(nèi)的單詞聋呢,返回 true
val allResult = list.all { it.length <= 5 } // 集合中是否所有單詞都在5個字母內(nèi)苗踪,返回 false
println("anyResult is $anyResult , allResult is $allResult")
5.3 Java 函數(shù)式 API 使用
在 Kotlin 代碼中調(diào)用 Java 方法,若該方法接收一個 Java 單抽象方法接口(接口中只有一個待實現(xiàn)方法)參數(shù)削锰,就可以使用函數(shù)式 API通铲。
如 Java 原生 API 中的 Runnable
接口就是一個單抽象方法接口:
public interface Runnable {
// 這個接口中只有一個待實現(xiàn)的 run() 方法
void run();
}
以 Java 的線程類 Thread
為例,Thread
類的構(gòu)造方法中接收一個 Runnable 參數(shù)器贩,Java 代碼創(chuàng)建并執(zhí)行一個子線程:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Thread is running");
}
}).start();
上面代碼用 Kotlin 實現(xiàn)如下:
Thread(object : Runnable {
override fun run() {
println("Thread is running")
}
}).start()
上面 Thread
類的構(gòu)造方法是符合 Java 函數(shù)式 API 使用條件的颅夺,因此可簡化為:
Thread(Runnable { println("Thread is running") }).start()
若一個 Java 方法的參數(shù)列表只有唯一一個單抽象方法接口參數(shù),可把接口名省略:
Thread({ println("Thread is running") }).start()
當(dāng) Lambda 表達(dá)式是方法的最后一個參數(shù)時蛹稍,可把它移到方法括號外面吧黄,同時如果它還是方法的唯一一個參數(shù),可把方法的括號省略:
Thread { println("Thread is running") }.start()
注:以上 Java 函數(shù)式 API 的使用都限定與從 Kotlin 中調(diào)用 Java 方法唆姐,并且單抽象方法接口也必須是 Java 語言定義的拗慨。
6. 空指針檢查
先看一段簡單的 Java 代碼:
public void doStudy(Study study){
study.readBooks();
study.doHomework();
}
若向 doStudy()
方法傳入一個 null
參數(shù),那么上面代碼就會報空指針異常奉芦,更加穩(wěn)妥的做法是做判空處理:
public void doStudy(Study study){
if (study != null){
study.readBooks();
study.doHomework();
}
}
若用 Kotlin 實現(xiàn)上面 doStudy()
函數(shù)赵抢,如下:
fun doStudy(study : Study){
study.readBooks();
study.doHomework();
}
它和 Java 版本沒啥區(qū)別,但它是沒有空指針風(fēng)險的声功,因為 Kotlin 默認(rèn)所有參數(shù)和變量都不可空烦却,當(dāng)你傳一個 null
參數(shù)時,編譯器會提示錯誤:
Kotlin 把空指針異常的檢查提前到了編譯時期先巴,程序若存在空指針異常的風(fēng)險其爵,那么在編譯時會直接報錯冒冬。
如果希望傳入的參數(shù)可為空,Kotlin 中在類名后面加一個問號就可以了摩渺,比如 Int
表示不可為空的整型窄驹,而 Int?
就表示可為空的整形。
把上面代碼中參數(shù)的類型由 Study
變?yōu)?Study?
证逻,如下:
發(fā)現(xiàn)調(diào)用 doStudy()
函數(shù)時可以傳入 null
參數(shù)了,但調(diào)用參數(shù)的兩個方法時抗斤,會出現(xiàn)紅色的錯誤提示囚企,這是因為把參數(shù)改成了可空的 Study?
類型,此時調(diào)用參數(shù)的 readBooks()
和 doHomework()
方法可能造成空指針異常瑞眼。
還需要做個判空處理龙宏,就不會出現(xiàn)錯誤了:
fun doStudy(study: Study?) {
if (study != null) {
study.readBooks()
study.doHomework()
}
}
當(dāng)然,用一些判空輔助工具會更加簡單進行判空處理伤疙,下面介紹幾個 Kotlin 的判空輔助工具:
- 操作符
?.
操作符 ?.
的作用是當(dāng)對象不為空時正常調(diào)用相應(yīng)的方法银酗,為空時則什么都不做。
用操作符 ?.
上述代碼可改為:
fun doStudy(study: Study?) {
study?.readBooks()
study?.doHomework()
}
- 操作符
?:
操作符 ?:
的左右兩邊都接收一個表達(dá)式徒像,若左邊表達(dá)式的結(jié)果不為空則返回左邊表達(dá)式的結(jié)果黍特,否則返回右邊表達(dá)式的結(jié)果。
比如把如下代碼:
val c = if (a != null) {
a
} else {
b
}
用操作符 ?:
就可簡化為:
val c = a ?: b
- 操作符
!!
操作符 !!
的作用是告訴 Kotlin 非常確信對象不會為空锯蛀,不需要 Kotlin 幫忙做空指針檢查灭衷,若出現(xiàn)問題再直接拋出空指針異常。
比如以下代碼:
上面代碼中 printUpperCase()
函數(shù)并不知道外部已經(jīng)對 content 進行了非空檢查旁涤,在調(diào)用 toUpperCase()
方法時翔曲,還認(rèn)為存在空指針風(fēng)險,從而編譯不通過劈愚。這種情況想要強制通過編譯瞳遍,可在對象的后面加上!!
,如下:
fun printUpperCase(){
val upperCase = content!!.toUpperCase(Locale.ROOT)
println(upperCase)
}
let
函數(shù)
let
函數(shù)提供了函數(shù)式 API 的編程接口菌羽,并將原始調(diào)用對象作為參數(shù)傳遞到 Lambda 表達(dá)式中掠械,如下:
obj.let { obj2 -> // 這里的 obj2 和 obj 是同一個對象
// 編寫具體的業(yè)務(wù)邏輯
}
let
函數(shù)屬于 Kotlin 中的標(biāo)準(zhǔn)函數(shù),可以處理全局變量的判空問題(if 判斷語句無法做到這一點)算凿,它配合操作符 ?.
可以在做空指針檢查時起到很大作用份蝴。
如上述的 doStudy()
函數(shù)代碼可用 let
函數(shù)進行優(yōu)化,如下:
fun doStudy(study: Study?) {
// study 對象不為空時就調(diào)用 let 函數(shù)氓轰,let 函數(shù)會將 study 對象本身作為參數(shù)傳遞到 Lambda 表達(dá)式中
study?.let { stu ->
stu.readBooks()
stu.doHomework()
}
}
當(dāng) Lambda 表達(dá)式的參數(shù)列表只有一個參數(shù)時婚夫,可不聲明參數(shù)名,用 it
關(guān)鍵字代替即可署鸡,從而可簡化為:
fun doStudy(study: Study?) {
study?.let {
it.readBooks()
it.doHomework()
}
}
7. Kotlin 中的小技巧
7.1 字符串內(nèi)嵌表達(dá)式
Kotlin 允許在字符串里嵌入 ${}
這種語法結(jié)構(gòu)的表達(dá)式案糙,并在運行時使用表達(dá)式執(zhí)行的結(jié)果替代這一部分內(nèi)容限嫌,大大提升了易讀性和易用性:
"hello, ${obj.name}, nice to meet you"
當(dāng)表達(dá)式僅有一個變量時,可將兩邊的大括號省略:
"hello, $name, nice to meet you"
舉個例子:
val name = "Wonderful"
val age = 18
println("My name is " + name + ", " + age + "years old")
用字符串內(nèi)嵌表達(dá)式的寫法可簡化為:
val name = "Wonderful"
val age = 18
println("My name is $name, $age years old")
7.2 函數(shù)的參數(shù)默認(rèn)值
Kotlin 中时捌,定義函數(shù)時給任意參數(shù)設(shè)定一個默認(rèn)值怒医,調(diào)用時就不會強制為此參數(shù)傳值,在此參數(shù)沒傳值的情況下使用設(shè)定的默認(rèn)值奢讨。如:
// 這里給第二個參數(shù) str 設(shè)定了個默認(rèn)值 “hello”
fun printParams(num: Int, str: String = "hello"){
println("num is $num , str is $str")
}
這樣調(diào)用時可不用給第二個參數(shù)傳值稚叹,如printParams(123)
。但如果改成給第一個參數(shù)設(shè)定默認(rèn)值的話:
// 這里給第一個參數(shù) num 設(shè)定了個默認(rèn)值 100
fun printParams(num: Int = 100, str: String){
println("num is $num , str is $str")
}
此時再調(diào)用諸如 printParams("world")
就會報類型匹配錯誤了拿诸,這時需要通過鍵值對的方式來傳參扒袖,從而不必按照參數(shù)定義的順序來傳參,如 printParams(str = "world")
亩码。
給函數(shù)設(shè)定參數(shù)默認(rèn)值這個功能季率,使得主構(gòu)造函數(shù)很大程度上替代了次構(gòu)造函數(shù),從而次構(gòu)造函數(shù)比較少使用到描沟。
本篇文章就介紹到這飒泻。