Kotlin學(xué)習(xí) 1 -- 快速入門(mén)Kotlin

本篇文章主要介紹以下幾個(gè)知識(shí)點(diǎn):

SUMMER DAY (圖片來(lái)源于網(wǎng)絡(luò))

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)鍵字:valvar

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ǔ)句主要用 ifwhen 語(yǔ)句,循環(huán)語(yǔ)句主要用 whilefor 循環(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)性修飾符比較如下:


Java 和 Kotlin 函數(shù)可見(jiàn)性修飾符對(duì)照表

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、SetMap,List 的主要實(shí)現(xiàn)類是 ArrayListLinkedListSet 的主要實(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ò)誤:

向 doStudy() 方法傳入 null 參數(shù)

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?,如下:

允許 Study 參數(shù)為空

發(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ù)比較少使用到。

本篇文章就介紹到這。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容