【碼上開學】Kotlin 裏那些「更方便的」

本期做者:java

視頻:扔物線(朱凱)git

文章:Sinyu(沈新宇)github

你們好,我是扔物線朱凱。這期是碼上開學的 Kotlin 基礎部分的第三篇(也是基礎部分的最後一篇):Kotlin 裏那些「更好用的」。老朋友話很少,先上視頻。數組

由於我一直沒有學會怎麼在掘金貼視頻,因此請點擊 這裏 去嗶哩嗶哩看,或者點擊 這裏 去 YouTube 看。安全

如下內容來自文章做者 Sinyubash

在上期內容當中,咱們介紹了 Kotlin 的那些與 Java 寫法不一樣的地方。這一期咱們再進階一點,講一講 Kotlin 中那些「更方便的」用法。這些知識點在不知道以前,你也能夠正常寫 Kotlin,可是在熟悉以後會讓你寫得更爽。閉包

構造器

主構造器

咱們以前已經瞭解了 Kotlin 中 constructor 的寫法:ide

🏝️
class User {
    var name: String
    constructor(name: String) {
        this.name = name
    }
}
複製代碼

其實 Kotlin 中還有更簡單的方法來寫構造器:函數

🏝️
               👇       
class User constructor(name: String) {
    // 👇 這裏與構造器中的 name 是同一個
    var name: String = name
}
複製代碼

這裏有幾處不一樣點:post

  • constructor 構造器移到了類名以後
  • 類的屬性 name 能夠引用構造器中的參數 name

這個寫法叫「主構造器 primary constructor」。與之相對的在第二篇中,寫在類中的構造器被稱爲「次構造器」。在 Kotlin 中一個類最多隻能有 1 個主構造器(也能夠沒有),而次構造器是沒有個數限制。

主構造器中的參數除了能夠在類的屬性中使用,還能夠在 init 代碼塊中使用:

🏝️
class User constructor(name: String) {
    var name: String
    init {
        this.name = name
    }
}
複製代碼

其中 init 代碼塊是緊跟在主構造器以後執行的,這是由於主構造器自己沒有代碼體,init 代碼塊就充當了主構造器代碼體的功能。

另外,若是類中有主構造器,那麼其餘的次構造器都須要經過 this 關鍵字調用主構造器,能夠直接調用或者經過別的次構造器間接調用。若是不調用 IDE 就會報錯:

🏝️
class User constructor(var name: String) {
    constructor(name: String, id: Int) {
    // 👆這樣寫會報錯,Primary constructor call expected
    }
}
複製代碼

爲何當類中有主構造器的時候就強制要求次構造器調用主構造器呢?

咱們從主構造器的特性出發,一旦在類中聲明瞭主構造器,就包含兩點:

  • 必須性:建立類的對象時,無論使用哪一個構造器,都須要主構造器的參與
  • 第一性:在類的初始化過程當中,首先執行的就是主構造器

這也就是主構造器的命名由來。

當一個類中同時有主構造器與次構造器的時候,須要這樣寫:

🏝️
class User constructor(var name: String) {
                                   // 👇 👇 直接調用主構造器
    constructor(name: String, id: Int) : this(name) {
    }
                                                // 👇 經過上一個次構造器,間接調用主構造器
    constructor(name: String, id: Int, age: Int) : this(name, id) {
    }
}
複製代碼

在使用次構造器建立對象時,init 代碼塊是先於次構造器執行的。若是把主構造器當作身體的頭部,那麼 init 代碼塊就是頸部,次構造器就至關於身體其他部分。

細心的你也許會發現這裏又出現了 : 符號,它還在其餘場合出現過,例如:

  • 變量的聲明:var id: Int
  • 類的繼承:class MainActivity : AppCompatActivity() {}
  • 接口的實現:class User : Impl {}
  • 匿名類的建立:object: ViewPager.SimpleOnPageChangeListener() {}
  • 函數的返回值:fun sum(a: Int, b: Int): Int

能夠看出 : 符號在 Kotlin 中很是高頻出現,它其實表示了一種依賴關係,在這裏表示依賴於主構造器。

一般狀況下,主構造器中的 constructor 關鍵字能夠省略:

🏝️
class User(name: String) {
    var name: String = name
}
複製代碼

但有些場景,constructor 是不能夠省略的,例如在主構造器上使用「可見性修飾符」或者「註解」:

  • 可見性修飾符咱們以前已經講過,它修飾普通函數與修飾構造器的用法是同樣的,這裏再也不詳述:

    🏝️
    class User private constructor(name: String) {
    // 👆 主構造器被修飾爲私有的,外部就沒法調用該構造器
    }
    複製代碼
  • 關於註解的知識點,咱們以後會講,這裏就不展開了

既然主構造器能夠簡化類的初始化過程,那咱們就幫人幫到底,送佛送到西,用主構造器把屬性的初始化也一併給簡化了。

主構造器裏聲明屬性

以前咱們講了主構造器中的參數能夠在屬性中進行賦值,其實還能夠在主構造器中直接聲明屬性:

🏝️
           👇
class User(var name: String) {
}
// 等價於:
class User(name: String) {
  var name: String = name
}
複製代碼

若是在主構造器的參數聲明時加上 var 或者 val,就等價於在類中建立了該名稱的屬性(property),而且初始值就是主構造器中該參數的值。

以上講了全部關於主構造器相關的知識,讓咱們總結一下類的初始化寫法:

  • 首先建立一個 User 類:

    🏝️
    class User {
    }
    
    複製代碼
  • 添加一個參數爲 nameid 的主構造器:

    🏝️
    class User(name: String, id: String) {
    }
    
    複製代碼
  • 將主構造器中的 nameid 聲明爲類的屬性:

    🏝️
    class User(val name: String, val id: String) {
    }
    
    複製代碼
  • 而後在 init 代碼塊中添加一些初始化邏輯:

    🏝️
    class User(val name: String, val id: String) {
        init {
            ...
        }
    }
    
    複製代碼
  • 最後再添加其餘次構造器:

    🏝️
    class User(val name: String, val id: String) {
        init {
            ...
        }
        
        constructor(person: Person) : this(person.name, person.id) {
        }
    }
    
    複製代碼

    當一個類有多個構造器時,只須要把最基本、最通用的那個寫成主構造器就好了。這裏咱們選擇將參數爲 nameid 的構造器做爲主構造器。

到這裏,整個類的初始化就完成了,類的初始化順序就和上面的步驟同樣。

除了構造器,普通函數也是有不少簡化寫法的。

函數簡化

使用 = 鏈接返回值

咱們已經知道了 Kotlin 中函數的寫法:

🏝️
fun area(width: Int, height: Int): Int {
    return width * height
}

複製代碼

其實,這種只有一行代碼的函數,還能夠這麼寫:

🏝️
                                      👇
fun area(width: Int, height: Int): Int = width * height

複製代碼

{}return 沒有了,使用 = 符號鏈接返回值。

咱們以前講過,Kotlin 有「類型推斷」的特性,那麼這裏函數的返回類型還能夠隱藏掉:

🏝️
// 👇省略了返回類型
fun area(width: Int, height: Int) = width * height

複製代碼

不過,在實際開發中,仍是推薦顯式地將返回類型寫出來,增長代碼可讀性。

以上是函數有返回值時的狀況,對於沒有返回值的狀況,能夠理解爲返回值是 Unit

🏝️
fun sayHi(name: String) {
    println("Hi " + name)
}

複製代碼

所以也能夠簡化成下面這樣:

🏝️
                       👇
fun sayHi(name: String) = println("Hi " + name)

複製代碼

簡化完函數體,咱們再來看看前面的參數部分。

對於 Java 中的方法重載,咱們都不陌生,那 Kolin 中是否有更方便的重載方式呢?接下來咱們看看 Kotlin 中的「參數默認值」的用法。

參數默認值

Java 中,容許在一個類中定義多個名稱相同的方法,可是參數的類型或個數必須不一樣,這就是方法的重載:

☕️
public void sayHi(String name) {
    System.out.println("Hi " + name);
}

public void sayHi() {
    sayHi("world"); 
}

複製代碼

在 Kotlin 中,也可使用這樣的方式進行函數的重載,不過還有一種更簡單的方式,那就是「參數默認值」:

🏝️
                           👇
fun sayHi(name: String = "world") = println("Hi " + name)

複製代碼

這裏的 world 是參數 name 的默認值,當調用該函數時不傳參數,就會使用該默認值。

這就等價於上面 Java 寫的重載方法,當調用 sayHi 函數時,參數是可選的:

🏝️
sayHi("kaixue.io")
sayHi() // 使用了默認值 "world"

複製代碼

既然與重載函數的效果相同,那 Kotlin 中的參數默認值有什麼好處呢?僅僅只是少寫了一些代碼嗎?

其實在 Java 中,每一個重載方法的內部實現能夠各不相同,這就沒法保證重載方法內部設計上的一致性,而 Kotlin 的參數默認值就解決了這個問題。

不過參數默認值在調用時也不是徹底能夠放飛自個人。

來看下面這段代碼,這裏函數中有默認值的參數在無默認值參數的前面:

🏝️
fun sayHi(name: String = "world", age: Int) {
    ...
}

sayHi(10)
// 👆 這時想使用默認值進行調用,IDE 會報如下兩個錯誤
// The integer literal does not conform to the expected type String
// No value passed for parameter 'age'

複製代碼

這個錯誤就是告訴你參數不匹配,說明咱們的「打開方式」不對,其實 Kotlin 裏是經過「命名參數」來解決這個問題的。

命名參數

具體用法以下:

🏝️
fun sayHi(name: String = "world", age: Int) {
    ...
}
      👇   
sayHi(age = 21)

複製代碼

在調用函數時,顯式地指定了參數 age 的名稱,這就是「命名參數」。Kotlin 中的每個函數參數均可以做爲命名參數。

再來看一個有很是多參數的函數的例子:

🏝️ 
fun sayHi(name: String = "world", age: Int, isStudent: Boolean = true, isFat: Boolean = true, isTall: Boolean = true) {
    ...
}

複製代碼

當函數中有很是多的參數時,調用該函數就會寫成這樣:

🏝️
sayHi("world", 21, false, true, false)

複製代碼

當看到後面一長串的布爾值時,咱們很難分清楚每一個參數的用處,可讀性不好。經過命名參數,咱們就能夠這麼寫:

🏝️
sayHi(name = "wo", age = 21, isStudent = false, isFat = true, isTall = false)

複製代碼

與命名參數相對的一個概念被稱爲「位置參數」,也就是按位置順序進行參數填寫。

當一個函數被調用時,若是混用位置參數與命名參數,那麼全部的位置參數都應該放在第一個命名參數以前:

🏝️
fun sayHi(name: String = "world", age: Int) {
    ...
}

sayHi(name = "wo", 21) // 👈 IDE 會報錯,Mixing named and positioned arguments is not allowed
sayHi("wo", age = 21) // 👈 這是正確的寫法

複製代碼

講完了命名參數,咱們再看看 Kotlin 中的另外一種常見函數:嵌套函數。

本地函數(嵌套函數)

首先來看下這段代碼,這是一個簡單的登陸的函數:

🏝️
fun login(user: String, password: String, illegalStr: String) {
    // 驗證 user 是否爲空
    if (user.isEmpty()) {
        throw IllegalArgumentException(illegalStr)
    }
    // 驗證 password 是否爲空
    if (password.isEmpty()) {
        throw IllegalArgumentException(illegalStr)
    }
}

複製代碼

該函數中,檢查參數這個部分有些冗餘,咱們又不想將這段邏輯做爲一個單獨的函數對外暴露。這時可使用嵌套函數,在 login 函數內部聲明一個函數:

🏝️
fun login(user: String, password: String, illegalStr: String) {
           👇 
    fun validate(value: String, illegalStr: String) {
      if (value.isEmpty()) {
          throw IllegalArgumentException(illegalStr)
      }
    }
   👇
    validate(user, illegalStr)
    validate(password, illegalStr)
}

複製代碼

這裏咱們將共同的驗證邏輯放進了嵌套函數 validate 中,而且 login 函數以外的其餘地方沒法訪問這個嵌套函數。

這裏的 illegalStr 是經過參數的方式傳進嵌套函數中的,其實徹底沒有這個必要,由於嵌套函數中能夠訪問在它外部的全部變量或常量,例如類中的屬性、當前函數中的參數與變量等。

咱們稍加改進:

🏝️
fun login(user: String, password: String, illegalStr: String) {
    fun validate(value: String) {
        if (value.isEmpty()) {
                                              👇
            throw IllegalArgumentException(illegalStr)
        }
    }
    ...
}

複製代碼

這裏省去了嵌套函數中的 illegalStr 參數,在該嵌套函數內直接使用外層函數 login 的參數 illegalStr

上面 login 函數中的驗證邏輯,其實還有另外一種更簡單的方式:

🏝️
fun login(user: String, password: String, illegalStr: String) {
    require(user.isNotEmpty()) { illegalStr }
    require(password.isNotEmpty()) { illegalStr }
}

複製代碼

其中用到了 lambda 表達式以及 Kotlin 內置的 require 函數,這裏先不作展開,以後的文章會介紹。

字符串

講完了普通函數的簡化寫法,Kotlin 中字符串也有不少方便寫法。

字符串模板

在 Java 中,字符串與變量之間是使用 + 符號進行拼接的,Kotlin 中也是如此:

🏝️
val name = "world"
println("Hi " + name)

複製代碼

可是當變量比較多的時候,可讀性會變差,寫起來也比較麻煩。

Java 給出的解決方案是 String.format

☕️
System.out.print(String.format("Hi %s", name));

複製代碼

Kotlin 爲咱們提供了一種更加方便的寫法:

🏝️
val name = "world"
// 👇 用 '$' 符號加參數的方式
println("Hi $name")

複製代碼

這種方式就是把 name 從後置改成前置,簡化代碼的同時增長了字符串的可讀性。

除了變量,$ 後還能夠跟表達式,但表達式是一個總體,因此咱們要用 {} 給它包起來:

🏝️
val name = "world"
println("Hi ${name.length}") 

複製代碼

其實就跟四則運算的括號同樣,提升語法上的優先級,而單個變量的場景能夠省略 {}

字符串模板還支持轉義字符,好比使用轉義字符 \n 進行換行操做:

🏝️
val name = "world!\n"
println("Hi $name") // 👈 會多打一個空行

複製代碼

字符串模板的用法對於咱們 Android 工程師來講,其實一點都不陌生。

首先,Gradle 所用的 Groovy 語言就已經有了這種支持:

def name = "world"
println "Hi ${name}"

複製代碼

在 Android 的資源文件裏,定義字符串也有相似用法:

<string name="hi">Hi %s</string> 

複製代碼
☕️
getString(R.id.hi, "world");

複製代碼

raw string (原生字符串)

有時候咱們不但願寫過多的轉義字符,這種狀況 Kotlin 經過「原生字符串」來實現。

用法就是使用一對 """ 將字符串括起來:

🏝️
val name = "world"
val myName = "kotlin"
           👇
val text = """ Hi $name! My name is $myName.\n """
println(text)

複製代碼

這裏有幾個注意點:

  • \n 並不會被轉義
  • 最後輸出的內容與寫的內容徹底一致,包括實際的換行
  • $ 符號引用變量仍然生效

這就是「原生字符串」。輸出結果以下:

Hi world!
    My name is kotlin.\n

複製代碼

但對齊方式看起來不太優雅,原生字符串還能夠經過 trimMargin() 函數去除每行前面的空格:

🏝️
val text = """ 👇 |Hi world! |My name is kotlin. """.trimMargin()
println(text)

複製代碼

輸出結果以下:

Hi world!
My name is kotlin.

複製代碼

這裏的 trimMargin() 函數有如下幾個注意點:

  • | 符號爲默認的邊界前綴,前面只能有空格,不然不會生效
  • 輸出時 | 符號以及它前面的空格都會被刪除
  • 邊界前綴還可使用其餘字符,好比 trimMargin("/"),只不過上方的代碼使用的是參數默認值的調用方式

字符串的部分就先到這裏,下面來看看數組與集合有哪些更方便的操做。

數組和集合

數組與集合的操做符

在以前的文章中,咱們已經知道了數組和集合的基本概念,其實 Kotlin 中,還爲咱們提供了許多使數組與集合操做起來更加方便的函數。

首先聲明以下 IntArrayList

🏝️
val intArray = intArrayOf(1, 2, 3)
val strList = listOf("a", "b", "c")

複製代碼

接下來,對它們的操做函數進行講解:

  • forEach:遍歷每個元素

    🏝️
    // 👇 lambda 表達式,i 表示數組的每一個元素
    intArray.forEach { i ->
        print(i + " ")
    }
    // 輸出:1 2 3 
    
    複製代碼

    除了「lambda」表達式,這裏也用到了「閉包」的概念,這又是另外一個話題了,這裏先不展開。

  • filter:對每一個元素進行過濾操做,若是 lambda 表達式中的條件成立則留下該元素,不然剔除,最終生成新的集合

    🏝️
    // [1, 2, 3]
          ⬇️
    // {2, 3}
    
    // 👇 注意,這裏變成了 List
    val newList: List = intArray.filter { i ->
        i != 1 // 👈 過濾掉數組中等於 1 的元素
    }
    
    複製代碼
  • map:遍歷每一個元素並執行給定表達式,最終造成新的集合

    🏝️
    // [1, 2, 3]
           ⬇️
    // {2, 3, 4}
    
    val newList: List = intArray.map { i ->
        i + 1 // 👈 每一個元素加 1
    }
    
    複製代碼
  • flatMap:遍歷每一個元素,併爲每一個元素建立新的集合,最後合併到一個集合中

    🏝️
    // [1, 2, 3]
                   ⬇️
    // {"2", "a" , "3", "a", "4", "a"}
    
    intArray.flatMap { i ->
        listOf("${i + 1}", "a") // 👈 生成新集合
    }
    
    複製代碼

關於爲何數組的 filter 以後變成 List,就留做思考題吧~

這裏是以數組 intArray 爲例,集合 strList 也一樣有這些操做函數。Kotlin 中還有許多相似的操做函數,這裏就不一一列舉了。

除了數組和集合,Kotlin 中還有另外一種經常使用的數據類型: Range

Range

在 Java 語言中並無 Range 的概念,Kotlin 中的 Range 表示區間的意思,也就是範圍。區間的常見寫法以下:

🏝️
              👇      👇
val range: IntRange = 0..1000 

複製代碼

這裏的 0..1000 就表示從 0 到 1000 的範圍,包括 1000,數學上稱爲閉區間 [0, 1000]。除了這裏的 IntRange ,還有 CharRange 以及 LongRange

Kotlin 中沒有純的開區間的定義,不過有半開區間的定義:

🏝️
                         👇
val range: IntRange = 0 until 1000 

複製代碼

這裏的 0 until 1000 表示從 0 到 1000,但不包括 1000,這就是半開區間 [0, 1000) 。

Range 這個東西,天生就是用來遍歷的:

🏝️
val range = 0..1000
// 👇 默認步長爲 1,輸出:0, 1, 2, 3, 4, 5, 6, 7....1000,
for (i in range) {
    print("$i, ")
}

複製代碼

這裏的 in 關鍵字能夠與 for 循環結合使用,表示挨個遍歷 range 中的值。關於 for 循環控制的使用,在本期文章的後面會作具體講解。

除了使用默認的步長 1,還能夠經過 step 設置步長:

🏝️
val range = 0..1000
// 👇 步長爲 2,輸出:0, 2, 4, 6, 8, 10,....1000,
for (i in range step 2) {
    print("$i, ")
}

複製代碼

以上是遞增區間,Kotlin 還提供了遞減區間 downTo ,不過遞減沒有半開區間的用法:

🏝️
// 👇 輸出:4, 3, 2, 1, 
for (i in 4 downTo 1) {
    print("$i, ")
}

複製代碼

其中 4 downTo 1 就表示遞減的閉區間 [4, 1]。這裏的 downTo 以及上面的 step 都叫作「中綴表達式」,以後的文章會作介紹。

Sequence

在上一期中咱們已經熟悉了 Sequence 的基本概念,此次讓咱們更加深刻地瞭解 Sequence 序列的使用方式。

序列 Sequence 又被稱爲「惰性集合操做」,關於什麼是惰性,咱們經過下面的例子來理解:

🏝️
val sequence = sequenceOf(1, 2, 3, 4)
val result: List = sequence
    .map { i ->
        println("Map $i")
        i * 2 
    }
    .filter { i ->
        println("Filter $i")
        i % 3  == 0 
    }
👇
println(result.first()) // 👈 只取集合的第一個元素

複製代碼
  • 惰性的概念首先就是說在「👇」標註以前的代碼運行時不會當即執行,它只是定義了一個執行流程,只有 result 被使用到的時候纔會執行

  • 當「👇」的 println 執行時數據處理流程是這樣的:

    • 取出元素 1 -> map 爲 2 -> filter 判斷 2 是否能被 3 整除
    • 取出元素 2 -> map 爲 4 -> filter 判斷 4 是否能被 3 整除
    • ...

    惰性指當出現知足條件的第一個元素的時候,Sequence 就不會執行後面的元素遍歷了,即跳過了 4 的遍歷。

List 是沒有惰性的特性的:

🏝️
val list = listOf(1, 2, 3, 4)
val result: List = list
    .map { i ->
        println("Map $i")
        i * 2 
    }
    .filter { i ->
        println("Filter $i")
        i % 3  == 0 
    }
👇
println(result.first()) // 👈 只取集合的第一個元素

複製代碼

包括兩點:

  • 聲明以後當即執行
  • 數據處理流程以下:
    • {1, 2, 3, 4} -> {2, 4, 6, 8}
    • 遍歷判斷是否能被 3 整除

Sequence 這種相似懶加載的實現有下面這些優勢:

  • 一旦知足遍歷退出的條件,就能夠省略後續沒必要要的遍歷過程。
  • List 這種實現 Iterable 接口的集合類,每調用一次函數就會生成一個新的 Iterable,下一個函數再基於新的 Iterable 執行,每次函數調用產生的臨時 Iterable 會致使額外的內存消耗,而 Sequence 在整個流程中只有一個。

所以,Sequence 這種數據類型能夠在數據量比較大或者數據量未知的時候,做爲流式處理的解決方案。

條件控制

相比 Java 的條件控制,Kotlin 中對條件控制進行了許多的優化及改進。

if/else

首先來看下 Java 中的 if/else 寫法:

☕️
int max;
if (a > b) {
    max = a;
} else {
    max = b;
}

複製代碼

在 Kotlin 中,這麼寫固然也能夠,不過,Kotlin 中 if 語句還能夠做爲一個表達式賦值給變量:

🏝️
       👇
val max = if (a > b) a else b

複製代碼

另外,Kotlin 中棄用了三元運算符(條件 ? 而後 : 不然),不過咱們可使用 if/else 來代替它。

上面的 if/else 的分支中是一個變量,其實還能夠是一個代碼塊,代碼塊的最後一行會做爲結果返回:

🏝️
val max = if (a > b) {
    println("max:a")
    a // 👈 返回 a
} else {
    println("max:b")
    b // 👈 返回 b
}

複製代碼

when

在 Java 中,用 switch 語句來判斷一個變量與一系列值中某個值是否相等:

☕️
switch (x) {
    case 1: {
        System.out.println("1");
        break;
    }
    case 2: {
        System.out.println("2");
        break;
    }
    default: {
        System.out.println("default");
    }
}

複製代碼

在 Kotlin 中變成了 when

🏝️
👇
when (x) {
   👇
    1 -> { println("1") }
    2 -> { println("2") }
   👇
    else -> { println("else") }
}

複製代碼

這裏與 Java 相比的不一樣點有:

  • 省略了 casebreak,前者比較好理解,後者的意思是 Kotlin 自動爲每一個分支加上了 break 的功能,防止咱們像 Java 那樣寫錯
  • Java 中的默認分支使用的是 default 關鍵字,Kotlin 中使用的是 else

if/else 同樣,when 也能夠做爲表達式進行使用,分支中最後一行的結果做爲返回值。須要注意的是,這時就必需要有 else 分支,使得不管怎樣都會有結果返回,除非已經列出了全部狀況:

🏝️
val value: Int = when (x) {
    1 -> { x + 1 }
    2 -> { x * 2 }
    else -> { x + 5 }
}

複製代碼

在 Java 中,當多種狀況執行同一份代碼時,能夠這麼寫:

☕️
switch (x) {
    case 1:
    case 2: {
        System.out.println("x == 1 or x == 2");
        break;
    }
    default: {
        System.out.println("default");
    }
}

複製代碼

而 Kotlin 中多種狀況執行同一份代碼時,能夠將多個分支條件放在一塊兒,用 , 符號隔開,表示這些狀況都會執行後面的代碼:

🏝️
when (x) {
    👇
    1, 2 -> print("x == 1 or x == 2")
    else -> print("else")
}

複製代碼

when 語句中,咱們還可使用表達式做爲分支的判斷條件:

  • 使用 in 檢測是否在一個區間或者集合中:

    🏝️
    when (x) {
       👇
        in 1..10 -> print("x 在區間 1..10 中")
       👇
        in listOf(1,2) -> print("x 在集合中")
       👇 // not in
        !in 10..20 -> print("x 不在區間 10..20 中")
        else -> print("不在任何區間上")
    }
    
    複製代碼
  • 或者使用 is 進行特定類型的檢測:

    🏝️
    val isString = when(x) {
        👇
        is String -> true
        else -> false
    }
    
    複製代碼
  • 還能夠省略 when 後面的參數,每個分支條件均可以是一個布爾表達式:

    🏝️
    when {
       👇
        str1.contains("a") -> print("字符串 str1 包含 a")
       👇
        str2.length == 3 -> print("字符串 str2 的長度爲 3")
    }
    
    複製代碼

當分支的判斷條件爲表達式時,哪個條件先爲 true 就執行哪一個分支的代碼塊。

for

咱們知道 Java 對一個集合或數組能夠這樣遍歷:

☕️
int[] array = {1, 2, 3, 4};
for (int item : array) {
    ...
}

複製代碼

而 Kotlin 中 對數組的遍歷是這麼寫的:

🏝️
val array = intArrayOf(1, 2, 3, 4)
          👇
for (item in array) {
    ...
}

複製代碼

這裏與 Java 有幾處不一樣:

  • 在 Kotlin 中,表示單個元素的 item ,不用顯式的聲明類型
  • Kotlin 使用的是 in 關鍵字,表示 itemarray 裏面的一個元素

另外,Kotlin 的 in 後面的變量能夠是任何實現 Iterable 接口的對象。

在 Java 中,咱們還能夠這麼寫 for 循環:

☕️
for (int i = 0; i <= 10; i++) {
    // 遍歷從 0 到 10
}

複製代碼

但 Kotlin 中沒有這樣的寫法,那應該怎樣實現一個 0 到 10 的遍歷呢?

其實使用上面講過的區間就能夠實現啦,代碼以下:

🏝️
for (i in 0..10) {
    println(i)
}

複製代碼

try-catch

關於 try-catch 咱們都不陌生,在平時開發中不免都會遇到異常須要處理,那麼在 Kotlin 中是怎樣處理的呢,先來看下 Kotlin 中捕獲異常的代碼:

🏝️
try {
    ...
}
catch (e: Exception) {
    ...
}
finally {
    ...
}

複製代碼

能夠發現 Kotlin 異常處理與 Java 的異常處理基本相同,但也有幾個不一樣點:

  • 咱們知道在 Java 中,調用一個拋出異常的方法時,咱們須要對異常進行處理,不然就會報錯:

    ☕️
    public class User{
        void sayHi() throws IOException {
        }
        
        void test() {
            sayHi();
            // 👆 IDE 報錯,Unhandled exception: java.io.IOException
        }
    }
    
    複製代碼

    但在 Kotlin 中,調用上方 User 類的 sayHi 方法時:

    🏝️
    val user = User()
    user.sayHi() // 👈 正常調用,IDE 不會報錯,但運行時會出錯
    
    複製代碼

    爲何這裏不會報錯呢?由於 Kotlin 中的異常是不會被檢查的,只有在運行時若是 sayHi 拋出異常,纔會出錯。

  • Kotlin 中 try-catch 語句也能夠是一個表達式,容許代碼塊的最後一行做爲返回值:

    🏝️
               👇       
    val a: Int? = try { parseInt(input) } catch (e: NumberFormatException) { null }
    
    複製代碼

?.?:

咱們在以前的文章中已經講過 Kotlin 的空安全,其實還有另一個經常使用的複合符號可讓你在判空時更加方便,那就是 Elvis 操做符 ?:

咱們知道空安全調用 ?.,在對象非空時會執行後面的調用,對象爲空時就會返回 null。若是這時將該表達式賦值給一個不可空的變量:

🏝️
val str: String? = "Hello"
var length: Int = str?.length
// 👆 ,IDE 報錯,Type mismatch. Required:Int. Found:Int?

複製代碼

報錯的緣由就是 str 爲 null 時咱們沒有值能夠返回給 length

這時就可使用 Kotlin 中的 Elvis 操做符 ?: 來兜底:

🏝️
val str: String? = "Hello"
                             👇
val length: Int = str?.length ?: -1

複製代碼

它的意思是若是左側表達式 str?.length 結果爲空,則返回右側的值 -1

Elvis 操做符還有另一種常見用法,以下:

🏝️
fun validate(user: User) {
    val id = user.id ?: return // 👈 驗證 user.id 是否爲空,爲空時 return 
}

// 等同於

fun validate(user: User) {
    if (user.id == null) {
        return
    }
    val id = user.id
}

複製代碼

看到這裏,想必你對 Kotlin 的空安全有了更深刻的瞭解了,下面咱們再看看 Kotlin 的相等比較符。

=====

咱們知道在 Java 中,== 比較的若是是基本數據類型則判斷值是否相等,若是比較的是 String 則表示引用地址是否相等, String 字符串的內容比較使用的是 equals()

☕️
String str1 = "123", str2 = "123";
System.out.println(str1.equals(str2));
System.out.println(str1 == str2); 

複製代碼

Kotlin 中也有兩種相等比較方式:

  • == :能夠對基本數據類型以及 String 等類型進行內容比較,至關於 Java 中的 equals
  • === :對引用的內存地址進行比較,至關於 Java 中的 ==

能夠發現,Java 中的 equals ,在 Kotlin 中與之相對應的是 ==,這樣可使咱們的代碼更加簡潔。

下面再來看看代碼示例:

🏝️
val str1 = "123"
val str2 = "123"
println(str1 == str2) // 👈 內容相等,輸出:true

val str1= "字符串"
val str2 = str1
val str3 = str1
print(str2 === str3) // 👈 引用地址相等,輸出:true

複製代碼

其實 Kotlin 中的 equals 函數是 == 的操做符重載,關於操做符重載,這裏先不講,以後的文章會講到。

練習題

  1. 請按照如下要求實現一個 Student 類:
    • 寫出三個構造器,其中一個必須是主構造器
    • 主構造器中的參數做爲屬性
    • 寫一個普通函數 show,要求經過字符串模板輸出類中的屬性
  2. 編寫程序,使用今天所講的操做符,找出集合 {21, 40, 11, 33, 78} 中可以被 3 整除的全部元素,並輸出。

做者介紹

視頻做者

扔物線(朱凱)
  • 碼上開學創始人、項目管理人、內容模塊規劃者和視頻內容做者。
  • Android GDE( Google 認證 Android 開發專家),前 Flipboard Android 工程師。
  • GitHub 全球 Java 排名第 92 位,在 GitHub 上有 6.6k followers 和 9.9k stars。
  • 我的的 Android 開源庫 MaterialEditText 被全球多個項目引用,其中包括在全球擁有 5 億用戶的新聞閱讀軟件 Flipboard 。
  • 曾屢次在 Google Developer Group Beijing 線下分享會中擔任 Android 部分的講師。
  • 我的技術文章《給 Android 開發者的 RxJava 詳解》發佈後,在國內多個公司和團隊內部被轉發分享和做爲團隊技術會議的主要資料來源,以及逆向傳播到了美國一些如 Google 、 Uber 等公司的部分華人團隊。
  • 創辦的 Android 高級進階教學網站 HenCoder 在全球華人 Android 開發社區享有至關的影響力。
  • 以後創辦 Android 高級開發教學課程 HenCoder Plus ,學員遍及全球,有來自阿里、頭條、華爲、騰訊等知名一線互聯網公司,也有來自中國臺灣、日本、美國等地區的資深軟件工程師。

文章做者

Sinyu(沈新宇)

Sinyu(沈新宇) ,即刻 Android 工程師。2019 年加入即刻,參與即刻 6.0 的產品迭代,以及負責中臺基礎建設。獨立開發並運營過一款用戶過萬的 App。

相關文章
相關標籤/搜索