Kotlin 知識梳理(2) 函式的定義與呼叫

澤毛發表於2017-12-21

一、本文概要

本文是對<<Kotlin in Action>>的學習筆記,如果需要執行相應的程式碼可以訪問線上環境 try.kotlinlang.org,這部分的思維導圖為:

Kotlin 知識梳理(2)   函式的定義與呼叫

二、在 kotlin 中建立集合

kotlin中,建立HashSetArrayListHashMap的方法如下:

Kotlin 知識梳理(2)   函式的定義與呼叫
通過列印這些集合的型別,可以看到是採用的標準的Java集合類:
Kotlin 知識梳理(2)   函式的定義與呼叫
這麼做的原因,是因為使用標準的Java集合使kotlin可以更容易地與Java程式碼互動。當從Kotlin呼叫Java函式的時候,不用轉換它的集合類來匹配Java的類,反之亦然。

在這些集合物件上,我們除了可以使用Java當中定義的基本函式以外,還可以使用kotlin提供的擴充套件方法,例如下面的lastmax

Kotlin 知識梳理(2)   函式的定義與呼叫
執行結果為:
Kotlin 知識梳理(2)   函式的定義與呼叫

三、讓函式更好呼叫

下面,我們定義一個函式,它的作用的是列印集合當中的元素,並指定元素之間新增分隔符、字首和字尾:

Kotlin 知識梳理(2)   函式的定義與呼叫
執行結果為:
Kotlin 知識梳理(2)   函式的定義與呼叫

3.1 命名引數

在上面的例子中,我們指定了分隔符、字首和字尾三個引數,但是對於這個函式的使用者來說,如果不去看這些函式的宣告,很難看出這些String型別的含義,這時候就可以使用 命名引數 的方法來呼叫,這有兩點好處:

  • 增加函式的可讀性
  • 以想要的順序指定需要的引數

下面,我們使用命名引數,並在不改變函式定義的情況下,改變傳入引數的順序,也可以得到和上面相同的執行結果:

Kotlin 知識梳理(2)   函式的定義與呼叫

對於命名引數,有以下幾點需要注意:

  • 如果在呼叫一個函式時,指明瞭一個引數的名稱,那它之後的所有引數都要表明名稱。
  • 當呼叫Java函式時,不能使用命名引數。

3.2 預設引數值

kotlin中,可以在宣告函式的時候,指定引數的預設值,這樣就可以避免建立過載的函式,例如上面的例子,我們可以在 定義函式 時,指定三個String的預設值,而在 呼叫函式 的時候,如果沒有傳遞這些引數,那麼將會採用預設值:

Kotlin 知識梳理(2)   函式的定義與呼叫
執行結果為:
Kotlin 知識梳理(2)   函式的定義與呼叫
但是要注意:

  • 如果使用常規的呼叫語法時,必須按照函式宣告中定義的引數順序來給定引數,可以省略的只有排在末尾的引數。
  • 如果使用命名引數,可以省略中間的一些引數,也可以以你想要的任意順序只給定你需要的引數,例如我們只修改字首和字尾,分隔符仍然採用預設值:
    Kotlin 知識梳理(2)   函式的定義與呼叫
    執行結果為:
    Kotlin 知識梳理(2)   函式的定義與呼叫

3.3 頂層函式和屬性

Java中,所有的程式碼都需要寫作類的函式,但是在專案中,很多程式碼並不能歸屬到類中,這時候我們一般會定義一個xxUtils類,並在其中宣告static的靜態方法。

kotlin中,我們可以把這些函式直接放到程式碼檔案的頂層,不用從屬於任何的類,這些放在檔案頂層的函式仍然是包內的成員,如果你需要從包外訪問它,則需要import,但不再需要額外包一層。

3.3.1 在 Java 中呼叫頂層函式

如果我們想要在Java中呼叫這些頂層函式,則需要通過Kotlin根據包含函式的檔案的名稱生成的類,例如我們有兩個檔案

  • KotlinMethod.kt
fun kotlinFunc() {}
複製程式碼
  • JavaCallKotlin.java
public class JavaCallKotlin {
    public JavaCallKotlin() {
        KotlinMethodKt.kotlinFunc();
    }
}
複製程式碼

前者包含一個頂層函式,那麼在Java中,就會根據該檔名生成一個{檔名}Kt的型別,再通過這個類來呼叫這個頂層函式。

如果不想使用預設的類名,可以在.kt檔案的開頭加上@file:JvmName("類名")的註解。

3.3.2 在 Java 中呼叫頂層屬性

和函式一樣,屬性也可以放到檔案的頂層。預設情況下,頂層屬性和其他任意的屬性一樣,是通過訪問器暴露給Java使用的(如果是val就只有一個getter,如果是var就對應gettersetter)。如果想要把一個常量以public static final的屬性暴露給Java,可以使用const來修飾它。

package com.demo.lizejun.kotlinsample.chapter1

//不可變。
val kotlinVal = "kotlinValue"
//可變。
var kotlinVar = "kotlinVariable"
//常量。
const val kotlinConst = "kotlinConst"
//頂層函式。
fun kotlinFunc() {}
複製程式碼

Java中,分別通過以下幾種方式來訪問或者修改這幾個頂層屬性:

package com.demo.lizejun.kotlinsample.chapter3;

import com.demo.lizejun.kotlinsample.chapter1.KotlinMethodKt;

public class JavaCallKotlin {
    public JavaCallKotlin() {
        KotlinMethodKt.kotlinFunc();
        //不可變。
        KotlinMethodKt.getKotlinVal();
        //可變。
        KotlinMethodKt.setKotlinVar("newKotlinVar");
        KotlinMethodKt.getKotlinVar();
        //常量。
        String kotlinConst = KotlinMethodKt.kotlinConst;
    }
}
複製程式碼

四、擴充套件函式和屬性

擴充套件函式 其實是一個類的成員函式,只不過它定義在類的外面,我們所需要做的,就是在宣告擴充套件函式的時候,把需要擴充套件的類或者介面的名稱,放到它的前面,用來呼叫這個擴充套件函式的物件,就叫做 接收者物件

在擴充套件函式中,可以直接訪問被擴充套件的類的其它方法和屬性,就好像是在這個類自己的方法中訪問它們的一樣,但是擴充套件函式不允許你打破它的封裝性,擴充套件函式不能訪問私有的或者是受保護的成員。

4.1 擴充套件函式的定義和使用

下面我們給String類新增一個擴充套件函式,返回它的最後一個字元:

Kotlin 知識梳理(2)   函式的定義與呼叫
執行結果為:
Kotlin 知識梳理(2)   函式的定義與呼叫

4.2 在 Java 中使用擴充套件函式

如果需要在Java中呼叫擴充套件函式,那麼把接收者物件作為第一個引數傳進去即可:

//接收者物件作為第一個引數。
char lastChar = KotlinMethodKt.last("Kotlin");
複製程式碼

4.3 不能重寫的擴充套件函式

這裡假設我們有兩個類,ViewButton,其中Button繼承於View,我們給這兩個類都新增一個名為showOff的擴充套件函式。

Kotlin 知識梳理(2)   函式的定義與呼叫
執行結果為:
Kotlin 知識梳理(2)   函式的定義與呼叫
儘管實際上這個變數是一個Button的物件,但是Kotlin會把擴充套件函式當做靜態函式來對待,因此 擴充套件函式不存在重寫

4.4 擴充套件屬性

擴充套件屬性提供了一種方法,用來擴充套件類的API,可以用來訪問屬性,用的是屬性語法而不是函式的語法,儘管他們被稱為屬性,但它們沒有任何狀態,因為沒有合適的地方來儲存它們。

現在,我們給StringBuilder新增一個可讀寫的屬性lastChar,用於獲取或者改變它的最後一個字元,包含以下幾點要素:

  • 擴充套件屬性以var/val關鍵字開頭
  • 指定擴充套件屬性的名字、型別
  • 如果是var那麼提供get()/set(value : T)方法,而如果是val屬性,那麼提供get()方法,其中T為屬性的型別。

Kotlin 知識梳理(2)   函式的定義與呼叫
執行結果為:
Kotlin 知識梳理(2)   函式的定義與呼叫

五、可變引數、中綴呼叫和庫的支援

5.1 可變引數

使用關鍵字 vararg,可以用來宣告一個函式將有可能有任意數量的引數,下面例子中,我們定義一個可以接收可變數量Int型別的函式,之後和在Java中一樣,它會被轉換為[I的整型陣列型別:

Kotlin 知識梳理(2)   函式的定義與呼叫
執行結果為:
Kotlin 知識梳理(2)   函式的定義與呼叫
而如果我們已經將引數打包成一個陣列,這時候如果想要將它傳遞給一個接收可變引數的函式,那麼需要先通過*操作符進行解包:
Kotlin 知識梳理(2)   函式的定義與呼叫
執行結果為:
Kotlin 知識梳理(2)   函式的定義與呼叫

5.2 中綴呼叫

中綴呼叫 不是特殊的內建結構,而是一種特殊的函式呼叫。在中綴呼叫中,沒有新增額外的分隔符,函式名稱是直接放在目標物件名稱和引數之間。例如我們宣告瞭一個to函式。

//一般函式呼叫。
1.to("One")
//中綴呼叫。
1 to "One"
複製程式碼

中綴呼叫可以與 只有一個引數的函式 一起使用,無論是普通的函式還是擴充套件函式,要允許使用中綴符號呼叫函式,需要使用infix修飾符來標記它,下面是一個建立Person的函式,我們採用了擴充套件函式的方法,這裡的AnyKotlin中所有類的父類,和Java中的Object相同:

Kotlin 知識梳理(2)   函式的定義與呼叫
執行的結果為:
Kotlin 知識梳理(2)   函式的定義與呼叫

六、字串處理

6.1 分割字串

Java中,我們會使用split來分割字串,它接受一個正規表示式作為引數。但是當我們想分割"."時,會得到一個空的陣列,因為.號表示任何字元的正規表示式。 而在kotlin中,它提供了一個接受Regex型別的過載函式,這樣確保了當有一個字串傳遞給這些函式的時候,不會被當做正規表示式,我們可以使用擴充套件函式toRegex將字串轉換為正規表示式。

Kotlin 知識梳理(2)   函式的定義與呼叫
執行結果為:
Kotlin 知識梳理(2)   函式的定義與呼叫

6.2 正規表示式

假設有下面這個字串

/Users/yole/kotlin-book/chapter.adoc
複製程式碼

我們需要通過這個字串獲取到chapter.adoc的目錄、檔名和副檔名,如果使用擴充套件函式,那麼程式碼如下:

Kotlin 知識梳理(2)   函式的定義與呼叫
執行結果為:
Kotlin 知識梳理(2)   函式的定義與呼叫
下面是使用正規表示式的做法:
Kotlin 知識梳理(2)   函式的定義與呼叫
這個正規表示式將一個路徑分為三個由斜線和點分隔的組:

  • .模式從字串的一開始就進行匹配,所以第一組(.+)包含最後一個斜線之前的子串,這和子串包含所有前面的斜線,因為它們匹配”任何字元“的模式。
  • 第二組包含最後一個點之前的子串
  • 第三組包含剩餘部分

七、區域性函式

Java的一個函式當中,有可能存在重複程式碼,例如在註冊模組中,可能需要校驗輸入的多個欄位是否有效,那麼校驗的邏輯就可以提取出一個函式,而Kotlin就提供了一種方法:可以在函式中巢狀這些提取的函式,區域性函式定義方式和普通函式是相同的。

Kotlin 知識梳理(2)   函式的定義與呼叫
執行的結果為:
Kotlin 知識梳理(2)   函式的定義與呼叫


更多文章,歡迎訪問我的 Android 知識梳理系列:

相關文章