【Kotlin】函式

little_fat_sheep發表於2024-04-06

1 常規函式

1.1 無參函式

fun main() {
    myFun()
}

fun myFun() {
    println("myFun") // 列印: myFun
}

1.2 有參函式

1)常規呼叫

fun main() {
    myFun("myFun") // 列印: myFun
}

fun myFun(str: String) {
    println(str)
}

2)形參指定預設值

fun main() {
    myFun() // 列印: abc
}

fun myFun(str: String = "abc") {
    println(str)
}

3)實參指定變數名

fun main() {
    myFun(b = 123, a = "abc") // 列印: abc123
}

fun myFun(a: String, b: Int) {
    println(a + b)
}

1.3 有返回值函式

1)常規呼叫

fun main() {
    var c = add(3, 5)
    println(c) // 列印: 8
}

fun add(a: Int, b: Int): Int {
    return a + b
}

​ 說明:對於無返回值型別函式,其返回型別為 Unit,如下,也可以省略不寫。

fun myFun(str: String): Unit {
    println(str)
}

2)單行函式體簡化

​ 當函式內部只有一行程式碼時,可以簡化如下。

fun main() {
    var c = add(3, 5)
    println(c) // 列印: 8
}

fun add(a: Int, b: Int): Int = a + b

1.4 可變長引數函式(vararg)

1)常規呼叫

fun main() {
    myFun("aa", "bb", "cc") // 列印: aa、bb、cc
}

fun myFun(vararg parms: String) {
    for (str in parms) {
        println(str)
    }
}

​ 說明:函式的可變長引數個數最多為 1 個。

2)使用陣列接收可變長引數

fun main() {
    myFun("aa", "bb", "cc") // 列印: 3
}

fun myFun(vararg parms: String) {
    var arr: Array<out String> = parms
    println(arr.size)
}

3)將陣列傳給可變長引數函式

fun main() {
    var arr: Array<String> = arrayOf("aa", "bb", "cc")
    myFun(*arr)  // 列印: 3
    myFun("xx", *arr, "yy")  // 列印: 5
}

fun myFun(vararg parms: String) {
    println(parms.size)
}

2 函式型別變數

2.1 函式型別變數

1)無參函式變數

fun test() {
    println("test")
}

fun main() {
    var myFun: () -> Unit = ::test
    myFun() // 列印: test
}

2)有參函式變數

fun test(a: Int, b: String): Unit {
    println("test, $a, $b")
}

fun main() {
    var myFun: (Int, String) -> Unit = ::test
    myFun(123, "abc") // 列印: test, 123, abc
}

3)有返回值函式變數

fun test(a: Int, b: Int): Int {
    return a + b
}

fun main() {
    var myFun: (Int, Int) -> Int = ::test
    println(myFun(3, 5)) // 列印: 8
}

2.2 匿名函式

​ 匿名函式即沒有名字的函式,在宣告函式變數時,可以指向一個匿名函式。

fun main() {
    var myFun: (Int, Int) -> Int = fun(a: Int, b: Int): Int {
        return a + b
    }
    println(myFun(3, 5)) // 列印: 8
}

​ 可以使用 Lambda 表示式簡化如下。

fun main() {
    var myFun: (Int, Int) -> Int = { a, b ->
        a + b
    }
    println(myFun(3, 5)) // 列印: 8
}

3 行內函數(inline)

​ 行內函數是使用 inline 關鍵字修飾的函式,編譯後會自動將函式體內的程式碼複製到呼叫處,以最佳化程式碼執行效率。

3.1 常規行內函數

​ Test.kt

fun main() {
    myFun()
}

inline fun myFun() {
    println("行內函數")
}

​ 以上程式碼經過編譯執行後,依次點選【Tools → Kotlin → Show Kotlin Bytecode】,生成位元組碼檔案。

img

​ 再點選 DeCompile 按鈕反編譯位元組碼檔案,會生成對應的 Java 檔案。

img

public final class TestKt {
   public static final void main() {
      String var1 = "行內函數";
      System.out.println(var1);
   }

   public static void main(String[] var0) {
      main();
   }

   public static final void myFun() {
      String var1 = "行內函數";
      System.out.println(var1);
   }
}

​ 說明:可以看到 myFun 函式里的程式碼被複制到呼叫處了。

3.2 帶 return 的巢狀行內函數

1)return 不帶標籤

fun main() {
    outFun {
        println("inFun")
        return // 等價於: return@main
    }
    println("main end") // 未列印
}

inline fun outFun(inFun: () -> Unit) {
    inFun()
    println("outFun end") // 未列印
}

​ 執行結果如下。

inFun

​ "outFun end" 和 "main end" 未列印,這是因為行內函數會直接將 return 語句複製到 main 函式中。

2)return@標籤

fun main() {
    outFun {
        println("inFun")
        return@outFun
    }
    println("main end")
}

inline fun outFun(inFun: () -> Unit) {
    inFun()
    println("outFun end")
}

​ 執行結果如下。

inFun
outFun end
main end

4 泛型函式

​ 泛型的型別檢查只存在於編譯階段,在原始碼編譯之後,不會保留任何關於泛型型別的內容,即型別擦除。

4.1 簡單泛型函式

1)單泛型引數

fun main() {
    myFun(123)  // 列印: 123
    myFun("abc")  // 列印: abc
    myFun(true)  // 列印: true
    myFun(null)  // 列印: null
}

fun <T> myFun(param: T) {
    println(param)
}

2)多泛型引數

fun main() {
    var res: Boolean = myFun("abc", 123, true) // 列印: abc, 123
    println(res) // 列印: true
}

fun <R, T, S> myFun(a: T, b: S, c: R): R {
    println("$a, $b")
    return c
}

4.2 類中泛型函式

fun main() {
    var c1: MyClass<String> = MyClass()
    c1.myFun("abc") // 列印: abc
    var c2: MyClass<Int> = MyClass()
    c2.myFun(123) // 列印: 123
}

class MyClass<T> {
    fun myFun(a: T) {
        println(a)
    }
}

4.3 自動推斷泛型型別

​ Kotlin 提供了下劃線(_)運算子可以自動推斷型別。

fun main() {
    myFun<Int, _>()
}

fun <S : Comparable<T>, T> myFun() {
    println("test _")
}

​ Int 類和 Comparable 類的定義如下。由於 Int 繼承了 Comparable,因此會自動推斷 "_" 為 Int。

public interface Comparable<in T>
public class Int private constructor() : Number(), Comparable<Int>

4.4 抗變、協變和逆變

1)抗變

​ 如下,Int 是 Number 的子類,Number 引用可以指向 Int 物件,但是 Data 引用不能指向 Data 物件,Data 引用也不能指向 Data 物件,該現象稱為抗變。

img

2)協變

​ 透過 out 關鍵字表示 Data 引用能指向 Data 物件,類似於 java 中的 "? extends Number"。

class Data<T>(var value: T)

fun main() {
    var data1: Data<Int> = Data<Int>(1)
    var data2: Data<out Number> = data1
    println(data2.value) // 列印: 1
    // data2.value = 1 // 編譯錯誤, setter方法被限制
}

​ 說明:協變後,不能修改協變元素。使用 out 修飾的泛型不能用作函式的引數,對應型別的成員變數 setter 方法會被限制,只能當做一個生產者使用。

3)逆變

​ 透過 in 關鍵字表示 Data 引用能指向 Data 物件,類似於 java 中的 "? super Int"。

class Data<T>(var value: T)

fun main() {
    var data1: Data<Number> = Data<Number>(1f)
    var data2: Data<in Int> = data1
    println(data2.value) // 列印: 1.0
    data2.value = 2
    var a: Any ?= data2.value // 只能用Any接收value
}

​ 說明:逆變後,只能使用 Any 接收逆變元素。使用 in 修飾的泛型不能用作函式的返回值,對應型別的成員變數 getter 方法會被限制,只能當做一個消費者使用。

4)通配

​ 在有些時候,我們可能並不在乎到底使用哪一個型別,我們希望一個變數可以接受任意型別的結果,而不是去定義某一個特定的上界或下界。在Kotlin 泛型中,星號(*)代表了一種特殊的型別投影,可以代表任意型別。

class Data<T>(var value: T)

fun main() {
    var data1: Data<Int> = Data<Int>(1)
    var data2: Data<*> = data1 // Data<*>等價於Data<out Any>
    println(data2.value) // 列印: 1.0
    // data2.value = 2 // 編譯錯誤, setter方法被限制
    var a: Any ?= data2.value // 只能用Any接收value
}

​ 說明:由於不確定具體型別,使用時只能是 Any 型別。

4.5 泛型的界

​ Kotlin 泛型中,可以為其指定上界。

1)單上界

class Data<T: Number>(var value: T)

fun main() {
    var data1: Data<Int> = Data<Int>(1)
    // var data1: Data<String> = Data<String>("abc") // 編譯錯誤, 指定了上界為Number
    var data2: Data<*> = data1 // Data<*>等價於Data<out Number>
    println(data2.value) // 列印: 1.0
    // data2.value = 2 // 編譯錯誤, setter方法被限制
    var a: Number = data2.value // 可以用Number接收value
}

2)多上界

open class A {}
interface B {}

class Data<T>(var value: T) where T: A, T: B

4.6 具化型別引數(reified)

​ Kotlin 的內聯(inline)函式可以使用 reified 關鍵字具化型別引數,允許在函式體內部檢測泛型型別,因為這些型別資訊會被編譯器內嵌在呼叫點。但是,這隻適用於行內函數,因為行內函數中的型別資訊在編譯時是可知的,並且實際型別會被編譯到使用它們的地方。

​ 以下呼叫會編譯報錯。

img

​ 透過 inline 和 reified 修飾符,可以解決編譯報錯問題,如下。

inline fun<reified T> isType(value: Any) : Boolean {
    return value is T
}

fun main() {
    println(isType<Int>("abc")) // 列印: false
    println(isType<String>("abc")) // 列印: true
}

​ 宣告:本文轉自【Kotlin】函式

相關文章