---
# System prepended metadata

title: Kotlin 函數 & 類
tags: [Kotlin]

---

---
title: 'Kotlin 函數 & 類'
disqus: kyleAlien
---

Kotlin 函數 & 類
===

## Overview of Content

2017 年開始，Kotlin 正式成為 Android 開發的第一級語言，與 Java 平起平坐，而且漸漸開始超越，Android 官方網站現在所給予的 Demo 都是先以 Kotlin 為第一優先，所以得硬起來學習... :smile:

以下參考，第一行代碼 (第三版), Kotlin 進階實戰

如有引用參考本文章請詳註出處，感謝 :smile:

:::success
* 如果喜歡讀更好看一點的網頁版本，可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/)
    
    本篇文章對應的是 [**Kotlin 函數、物件導向 | DataClass、Sealed、Object 關鍵字 | Enum、Companion、NPE**](https://devtechascendancy.com/kotlin-functions_oop_dataclass_sealed_object/)
:::


[TOC]


## Kotlin 函數

在 Kotlin 中函數是第一公民 (`First-Class Citizen`)，這導致 Kotlin 有以下特色

1. `Function` 可作為變數（可存取）

2. `Function` 可做為參數傳遞

3. `Function` 可以作為回傳（並非像是回傳類，它是回傳一個函數）

4. `Function` 可在 Runtime 時構造

5. `Function` 可表示為匿名字面值


### 函數 - 參數

* Kotlin 使用關鍵字 `fun`，無論定義啥函數都必須以 `fun` 關鍵字開頭，之後才接上函數名稱、參數，最後使用 `:` 接上返回值 (若沒有返回值則可忽略)

    :::info
    這種參數表達是 **使用了 [`Pascal` 語言的](https://zh.wikipedia.org/wiki/Pascal%E8%AA%9E%E8%A8%80) 表示法**
    :::

    > ![](https://i.imgur.com/Wx0Lo5a.png)

    1. **默認參數**：默認參數方便於不用過多的重載函數，也就是你不需重複寫相同函數名，但不同參數的函數（Overload Function）

        ```kotlin=
        fun defaultParams(times : Int = 10, info : String = "TEST-Params") {
            for (i in 0 until times) {
                println("$info - $i")
            }
        }

        fun main() {
            // 使用預設參數
            defaultParams()
        }
        ```
        > ![](https://i.imgur.com/kr0v9g3.png)        

        :::warning
        * Java 無法調用 Koltin 的默認參數怎麼辦？

            > ![](https://i.imgur.com/LaRX3Te.png)

            **使用 `@JvmOverloads` 註解**，它會幫我們產生對應的重載函數讓 Java 調用

            ```kotlin=
            @JvmOverloads        // 使用註解
            fun defaultParams(times : Int = 10, info : String = "TEST-Params") {
                for (i in 0 until times) {
                    println("$info - $i")
                }
            }
            ```
            > ![](https://i.imgur.com/YT0yCc4.png)

        :::

    2. **命名（指定）參數**：可以直接指定參數做傳入，其中也可以配合預設參數
    
        ```kotlin=
        fun namedParams(first: Int, second : Int, 
                        info: String = "Hello Kotlin") {

            for(i in 0 until first) {
                for(j in 0 until second) {
                    println("First: $i, Second: $j, Info: $info")
                }
            }

        }
        
        fun main() {
            // 指定參數
            namedParams(second = 3, first = 2)
        }
        ```
        > ![](https://i.imgur.com/LeBbOeC.png)

    
    3. **可變量參數**：可變參數等同於 Java 中的 `...` ，而在 Kotlin 中要使用 `vararg` 關鍵字
    
        ```kotlin=
        fun <T> toList(vararg items : T): ArrayList<T> {
            val res = ArrayList<T>()

            for(i in items) {
                res.add(i)
            }
            return res
        }

        fun main() {
            println("${toList("Hello", "Kotlin", "123")}")
        }
        ```

        >![](https://i.imgur.com/DJhwxU0.png)

        :::warning
        * Kotlin 不能直接傳遞數組，所以必須使用 `*` 符號來解包

            ```kotlin=
            fun main() {
                val array : Array<String> = arrayOf("1", "2", "3")
                val res = toList(*array)
                println(res)
            }
            ```
            > ![](https://i.imgur.com/K3xbILq.png)
        
        * Java 有規定可變常數必須放置最後一個參數，而 Kotlin 則沒有這個規範，但 **如果放置在非低一參數，則需要使用命令參數指定**

            ```kotlin=
            fun <T> toList_2(vararg items : T, other : T): ArrayList<T> {
                val res = ArrayList<T>()

                for(i in items) {
                    res.add(i)
                }

                res.add(other)
                return res
            }


            fun main() {
                val list = toList_2("1", "2", "3", other = "HelloWorld")
                println(list)
            }
            ```
            > ![](https://i.imgur.com/PB4vlk9.png)

        :::

    :::info
    * **函數** & **方法** ?

        其實 `函數 (function)` & `方法(method)` 是同一個概念，只是在 Java 中較常使用 method，Kotlin 中較常使用 function 來稱呼
        > 若再細分可以把方法歸類為，必須寫在類中的 function，而 Kotlin 可以把 funtion 寫在類之外 (Java 不行)
    :::


### 函數 - 返回值

1. 返回 Unit：Kotlin 沒有所謂的 void，但 Kotlin 可以返回 `Unit`

    ```kotlin=
    fun printlnMyInfo() : Unit {    // 可被省略
        println("Hello Kotlin")
    }

    fun main() {
        printlnMyInfo()   
    }
    ```
    
    :::success
    * 從 Unit 源碼可以看出 Unit 返回的是一個單例對象

        ```kotlin=
        public object Unit {
            override fun toString() = "kotlin.Unit"
        }
        ```
    :::
    
2. 返回 Nothing：Nothing 與 Unit 很相像，不過 Nothing 則是代表進入該函數後，**絕對不會返回**

    ```kotlin=
    fun printlnMyInfoNothing() : Nothing {
        while (true) {
            println("Hello Kotlin Nothing")
        }
    }
    ```
    
    :::success
    * Kotlin 的源碼，可以看出它是一個類

        ```kotlin=
        public class Nothing private constructor()
        ```
    :::
    

### 函數表達式
* 以下說明 Kotlin 函數的特殊表達方式

    1. **單表達式**：若函數只有一行，則可以使用 `=` 符號，可省略 ^1.^ 大括號、^2.^ return 關鍵字、^3.^ 返回類型 (會自動推倒)

        ```java=
        /** 函數 & 方法 都是指相同
         *  函數 -> function  (Kotlin 常用
         *  方法 -> method    (Java 常用
         */
        fun main() {
            printA()
            printB()

            println("compare result : ${compareMax(10, 20)}")
            println("compare result : ${compareMin(10, 20)}")
        }

        // 關鍵字 fun，使用 ':' 定義返回類型
        fun printA() {    // 返回 void，可以不用特別寫返回
           println("Hello Kotlin!")
        }

        fun printB() : Unit {    // 由於 Kotlin Function 返回不可使用 void，所以可以使用 Unit 替代
            println("Hello Kotlin Function!")
        }

        fun compareMax(a: Int, b: Int) : Int {
            return max(a, b)
        }

        /**
         * fun 語法糖，當只有一行內容就可以省略以下敘述
         * 1. 大括號
         * 2. 若需要 return 則可直接省略
         * 3. 不用顯式的聲明返回類型，返回類型可以推導
         */
        fun compareMin(a: Int, b: Int) = min(a, b)
            
        // fun compareMin(a: Int, b: Int) : Int = min(a, b)   
        // 同上，只是多了返回類型
        ```
        > ![](https://i.imgur.com/F31F6jK.png)

    2. **局部函數**（Local Function）：在函數內再定義函數，如同 Python 一樣

        ```kotlin=
        fun localFunction_print(string: String) {

            fun checkBlank() {
                if (string.isBlank()) throw IllegalArgumentException("Don't input blank.")
            }

            checkBlank()
            println("$string be print.")
        }

        fun main() {
            localFunction_print("LocalFunction")

            localFunction_print("")
        }
        ```
        > ![](https://i.imgur.com/0lbspoT.png)

    3. **尾遞歸函數**：其實這句話由兩個動作組成，^1.^ 尾函數（在 Function 中最後呼叫的函數）, ^2.^ 遞歸

        * 以往我們在使用遞歸時總是會要注意遞歸深度問題，如果遞歸深度過身則會導致 StackOverflowError (如下)

            ```kotlin=
            fun sumTimesNoTailrec(n: Int, result : Int) : Int =
                if(n < 0) result else sumTimes(n - 1, result + n)
                
            fun main() {
                println("NoTailrec Result: ${sumTimesNoTailrec(100000, 0)}")
            }
            ```
            > 

        * **使用 `tailrec` 關鍵字**：該關鍵字的主要目標是優化遞迴的程式，使其不會 StackOverflowError


            ```kotlin=
            tailrec fun sumTimes(n: Int, result : Int) : Int =
                if(n < 0) result else sumTimes(n - 1, result + n)

            fun main() {
               println("Result: ${sumTimes(100000, 0)}")
            }
            ```
            > ![](https://i.imgur.com/LdtefWx.png)

    4. **Top-level 函數**：Java 中我們必須將函數定義在某個類中，不能寫在檔案頂層，而 Kotlin 可以

        頂層函數默認是 `public` 可以被任意訪問 (當然也可以修改它的訪問權限)

        ```kotlin=
        private fun printlnHelloWorld() = println("Hello World")
        ```
        
        :::success
        * Java 可以訪頂層函數？ 可以

            1. 透過 `<檔案名>Kt` 訪問

                ```java=
                // Kt File          
                package base.function
                fun printlnHelloWorld() = println("Hello World")
                    
                // Java File
                public static void main(String[] args) {   
                    Function_SpecialKt.printlnHelloWorld();
                }
                ```
            
            
            2. 透過 `@file:JvmName("自訂名稱")` 註解，**==該註解一定要放在文件頂端==！ 並且不可與第一個方法混用**
                ```java=
                // Kt File
                @file:JvmName("HelloUtils")             
                package base.function
                
                fun printlnHelloWorld2() = println("Hello World 2")
                    
                // Java File
                public static void main(String[] args) {   
                    Function_SpecialKt.printlnHelloWorld();
                }
                ```
        :::




## 物件導向
其與 C 有最大的不同在於，物件導向是可以依照一個模板來創建類的 (可創建多個)，我們可以把很多事物一起做封裝，用來描述事物(冰箱、電視、調查表...)

| 程式表達 | 描述 |
| -------- | -------- |
| 字段 Field | 資料屬性 |
| 函數 Method | 行為動作 |

:::info
* Kotlin 的一般類都是 `final` 類 （修正 Java 的不安全、不強制，這有助於設計的完整性）
:::

### 類 - 創建方式

* Kotlin 較特別的特點如下

    1. **可以單獨創造類 (Class) 而不需實做**
        
        ```kotlin=
        // 可以單獨創建該類，而不需要大括號
        class MyClass
        ```
    
    
    2. **省去 `new` 關鍵字**

        ```kotlin=
        class PC {
            var CPU = "Intel-core-i5";
            var ram = 16;

            fun info() {
                println("Cpu: $CPU, ram: $ram")
            }
        }

        /**
         * 實例化一個類，不需要 new 關鍵字
         */
        fun main() {

            val p = PC()    // 省略 new

            p.info();
        }
        ```

        > ![](https://i.imgur.com/Otniakb.png)

### 類 - 建構函數

* 構造函數與 Java 較不同的在於，**Kotlin 構造函數有分為 ++主構造函數++、++次構造函數++**；主構造函數沒有函數體，必須透過 `init 結構` 來完成 
    
    > 類似於 Java 的 static 區塊，但是 **init 區塊是每次建構對象都會呼叫**

    1. **主構造函數**：使用 `init { }`，主構造函數可以省略 constructor 關鍵字
    
        ```kotlin=
        class MyClz_1 {
            
            init {    // 主構造函數
                println("init - primary construct")
            }

        }

        fun main() {
            MyClz_1()
        }
        
        // ----------------------------------------------------------
        // 同上
        class MyClz_2 constructor() {    // 主構造函數 (沒有參數時可以省略)
            init {
                println("init - primary construct")
            }

        }
        ```
        > ![](https://i.imgur.com/lcM0MG6.png)

        

    2. **次構造函數**：次構造函數必須呼叫主構造函數，使用 `:this(...)`
        
        主構造函數可以使用 `val`, `var` 修飾屬性，修飾屬性後就可以轉為該類的自身屬性

        ```kotlin=
        class MyClz_3 constructor(val str1: String){    // 主建構函數
            init {    
                println("init - primary construct, $str1")
            }

            var str2 : String? = null

            // 次構造函數 (必須呼叫主構造函數)
            constructor(str1 : String, str2 : String) : this(str1) {
                this.str2 = str2
            }

            fun printInfo() = println("Str1: $str1, Str2: $str2")
        }

        fun main() {

            MyClz_3("Hello").printInfo()

            MyClz_3("Hello", "World").printInfo()

        }
        ```
        > ![](https://i.imgur.com/xDCdjOZ.png)

        :::success
        * `init { }` 區塊可以有多個區塊，該區塊會按照上到下的順序被調用，接著才會輪到次構造函數

            ```kotlin=
            class MyClz_4 constructor(val str1: String){
                init {    // 主構造函數
                    println("init - primary construct, $str1 + _1")
                }
                init {    // 主構造函數
                    println("init - primary construct, $str1 + _2")
                }

                var str2 : String? = null

                constructor(str1 : String, str2 : String) : this(str1) {
                    this.str2 = str2

                    println("Second construct")
                }

                init {    // 主構造函數
                    println("init - primary construct, $str1 + _3")
                }

            }

            fun main() {
                MyClz_4("Hello", "Kotlin")
            }
            ```
            
            > ![](https://i.imgur.com/FoxnCxH.png)
        :::

### 類 - 屬性

* 類的「屬性」可以理解為類的成員，但是比起成員，我們可以對屬性添加而外的 setter/getter操作；在 Kotlin 中可以透過對於屬性的設定限制屬性的存取，其格式如下
    ```shell=
    ## var 屬性格式
    
    var <propertyName> [: <PropertyType>] [= <Initializer>]
        [<getter>]
        [<setter>]
        
    ##-----------------------------------------------------##
    ## val 屬性格式
    
    val <propertyName> [: <PropertyType>] [= <Initializer>]
        [<getter>]
    ```

* Kotlin Property 範例

    ```kotlin=
    class HttpResponse {
        var resCode = -1

        val isPass : Boolean
            get() = resCode == 200
    }

    fun main() {
        val res = HttpResponse()
        res.resCode = 200

        println("res: ${res.isPass}")
    }
    ```
    > ![](https://i.imgur.com/s1H8gY6.png)

* **Backing field** 幕後字段：它是 Kotlin 屬性自動生成的字段，它只能在當前屬性的訪問器內使用 （拓展類不可使用）
    
    :::danger
    * 無法自己調用自己，會導致遞迴
        
        ```kotlin=
        // Error
        var paramValue : Int = 0
            get() = paramValue
            set(value) = this.paramValue = value
        ```
        
        > ![](https://i.imgur.com/mkFCeGg.png)

    :::

    ```kotlin=
    class BackingField {
        var paramValue : Int = 0
            get() {
                // 自動產生 field 字段
                println("Get: $field")
                return field + 1
            }
            set(value) {
                // 自動產生 field 字段
                println("Set: $value")
                field = value - 3
            }
    }

    fun main() {
        val bf = BackingField()
        bf.paramValue = 10
        println(bf.paramValue)
    }
    ```
    > ![](https://i.imgur.com/XozWtv7.png)

### 抽象 - abstract

* 抽象類跟 Java 很像，同樣使用 `abstract` 關鍵字

    :::success
    使用 `abstract` 關鍵字 就自動轉為 `open` 型態的類
    :::

    ```kotlin=
    package class_2

    fun main() {
        val p1 = Man()
        p1.say()
    }

    interface IHello {
        fun say()
    }

    abstract class Person : IHello {
    }

    /**
     * 抽象則 "必須" 使用 ()
     */
    class Man : Person() {
        override fun say() {
            println("Hello World")
        }
    }
    ```
    **--實做結果--**
    > ![](https://i.imgur.com/MZK1NxE.png)


### 內部類 - class / inner class 差別

* 內部 class 又分為 ^1.^ 靜態 class(與外部類較無關係)、^2.^ 一般 class (必須要使用實例化的外部類才能創建，與外部類關係較大，但是可以直接使用外部元素)

    ```kotlin=
    fun main() {
        val t = Test()
        val t1 = Test.TestInner()
        val t2 = t.TestInner2()
        t1.show()
        t2.show()
    }

    class Test {
        val A = 1234

        companion object {  // 相當於靜態區塊 static{ }
            val B = 5678
        }

        /**
         * 相當於靜態內部類 static class
         */
        class TestInner {
            fun show() {
    //            println("A is $A")  // Error
                println("static class, B is $B")    // 可以獲取靜態外部元素
            }
        }

        /**
         * 必須要使用 inner 關鍵字才能讓內部類 & 外部類產生關係
         */
        inner class TestInner2 {
            fun show() {
                println("inner class, A is $A")  //
            }
        }

    }
    ```
    :::info
    Kotlin class 內部的 class 預設為 `static final class`，它不能訪問外部引用
    :::
    
* 我們將上面的內部類轉為 Java 看看

    1. class 靜態內部類，靜態內部類 `static final class` 類
        
        ```java=
        // Java 的靜態內部類

        public static final class TestInner 

            public final void show() {
                // 無法直接取得外部引用
                String var1 = "static class, B is " + Test.Companion.getB();
                boolean var2 = false;
                System.out.println(var1);
            }
        }
        ```

    2. **inner class 是一般內部類**，而一般內部類為靜態 `final class` 類

        ```java=
        // Java 的一般內部類

        public final class TestInner2 {
            public final void show() {
                // 可以使用 this 取得外部引用
                String var1 = "inner class, A is " + Test.this.getA();
                boolean var2 = false;
                System.out.println(var1);
            }
        }
        ```

    **--實作結果--**
    > ![](https://i.imgur.com/ZfOArkE.png)

### 類的可見性
* **Kotlin 默認所有的參數皆為 public**

    | 修飾符 | Java | Kotlin |
    | -------- | -------- | -------- |
    | 無 | **預設**，同路靜下的類可見 |  |
    | private | 當前類可見 | 當前類可見 |
    | protected | 同一個路徑包可見 | 同一個路徑包可見 |
    | public | 全部域可見 | **預設**，全部域可見 |

    :::success
    * Kotlin 還有另外一些修飾符 **internal、inner**
        
        1. **internal**：若是希望該類不會被外部調用則可以使用，就像是 Java 的內部類
            
            > `internal` 如果使用在外部類，其特別之處在於僅限「同模組內可調用」

            ```java=
            // java 版本
            public class A {
                // internal 就像是私有內部類
                private static class C {

                }
            }

            // kt 版本
            class A {
                internal class C {        // 使用 internal 關鍵字，內部不公開

                }
            }
            ```
        
        2. **inner**：一般非靜態內部類 (另個小節回說到)，外部函數可使用
            ```java=
            // java 版本
            public class A {
                // inner 就像是內部類
                public class B {

                }
            }

            // kt 版本
            class A {
                inner class B {        // 使用 inner 關鍵字，內部公開

                }
            }
            ```

    :::
### Enum 類

* Kotlin 的 Enum 類與 Java 類似，這裡我們可以配合上面所學的主建構函數來為 Enum 類添加屬性

    ```kotlin=
    enum class EnumClz(val describe: String, val number: Int) {
        PAN("Pan", 1),
        KYLE("Kyle", 2),
        ALIEN("Alien", 3);

        fun printInfo() = println("describe: $describe, number: $number")
    }

    fun main() {
        for (i in EnumClz.values()) {
            i.printInfo()
        }
    }
    ```
    > ![](https://i.imgur.com/HVshr7F.png)

## 類的應用

### 繼承 - Class

* **==Kotlin 默認類是不可以繼承的，也就用是 final 描述類==，若要繼承需要使用關鍵字 ==`open`== 打開這個類**；但若 Class 本來就是 `abstruct` 的類那就原本就是 open 的 (這也滿符合語意的)

* 在繼承中，若是沒有主構造函數，**只有次構造函數**，則必須使用 **`super`** 呼叫父類的建構函數


    ```kotlin=
    package class_2
    /**
     * open 關鍵字
     * 是由於 class 預設是 final class，也就是不可繼承
     * 使用 open 關鍵字就可以解開 final class 的預設 (抽象 class 本身就沒有 final 關鍵字)
     */

    open class Info    // 屬性跟類都可以使用 private 修飾
        (var id: Long, private var name: String) {

        fun showInfo() {
            println("id: $id, name: $name")
        }
    }

    /**
     * 繼承使用 `:` 符號，並且父類必須加 `()`，這有關係到主、次 construct
     * 主建構函數，要呼叫必須使用 init {} 結構
     * 次建構函數，使用 constructor()，並且可以函數重載 (Dart 就不行)
     */
    class Boy(name: String) : Info(123, name) {       // 可以選擇繼承的構造函數，這裡選次建構函數

        constructor() : this("Boy")     // this 呼叫自身
        
        constructor(id: Long) : this("Boy") {     // this 呼叫自身
            this.id = id
        }
        constructor(id: Long, name: String) : this(name) {         // this 呼叫自身
            this.id = id
        }
    }
    
    class Girl : Info {
        // 若是沒有主構造函數，只有次構造函數，則必須使用 super 呼叫父類的建構函數
        
        // super 呼叫 Parent constructor
        constructor(id: Long) : super(id, "Girl")  // 可以呼叫次 or 主 construct
    }

    fun main() {
        // 實例化不需要 new 關鍵字
        val m : Info = Boy()
        m.showInfo();

        Boy().showInfo()
        Boy(11).showInfo()
        Boy(22, "Pan").showInfo()

        Girl(33).showInfo()
    }
    ```

    :::info
    * **在主構造函數裡面宣告的 val、var 字段，會自動轉為該類別的字段**，所以繼承者不可再用 val、var 在主構造函數中宣告相同的屬性 (因為名稱重複會衝突，但是一般屬性可以)

        ```kotlin=
        package class_2

        /**
         * 建構函數參數，必須要定義型態
         */
        open class Shape(val width: Int, val height: Int) {    // width、height 是成員元素
            fun  printSize() {
                println("width: $width, height: $height")
            }
        }


        class Circle(radius: Int, width: Int) : Shape(radius, radius) {
        // 上面 okay 的原因在於 width 還沒有成為該類屬性

            // width 變數重複
            // class Circle(radius: Int, var width: Int) : Shape(radius, radius)
            fun CircleSize() {
                // 這邊所指的是父類的 (Shape 類的 width)
                println("width: $width, height: $height")
            }
        }

        fun main() {
            val c = Circle(10, 1)
            c.printSize()
        }
        ```
        以下演示錯誤，width 成員重複
        > ![](https://i.imgur.com/iIULYJr.png)

    :::


**--實作結果--**
> ![](https://i.imgur.com/cGWc2c2.png)


### 介面實做 - interface
* Kotlin 也有 `interface` (Dart 語言就沒有)，使用方法如同 Java，內部就可以宣告需要實做的 function 

    > 介面與繼承的順序可以隨意顛倒 (Java 則是有規定要先有繼承，才能有 `interface`)

    ```kotlin=
    interface IInfo {
        fun printInfo()
    }

    open class Person(var name: String, var age: Int) {
    }

    // 接口與繼承的順序可以顛倒
    class Student(name: String, age: Int) : IInfo,  Person(name, age) {  
        // Kotlin override 是放置在函數前
        override fun printInfo() {
            println("name: $name, age: $age")
        }
    }

    fun main() {
        Student("Alien", 18).printInfo()
    }
    ```

    **--實做結果--**
    > ![](https://i.imgur.com/qTOfbdB.png)

## 類 - object 關鍵字

object 關鍵字使用在物件聲明，物件表達，伴生類

### 靜態區塊（伴生類） - companion object

:::warning
Kotlin 中沒有 static 關鍵字，在其中也沒有靜態函數 & 屬性
:::

* **`companion object {}` 區塊相當於 Java 中的 `static {}` 靜態區塊，會在類加載成功後就存在**，並且只會加載一次

    ```cpp=
    class Person(var name: String, var age: Int) {

        companion object {
            var a : Int = 10

            fun hello() : Unit {
                println("Person: $a")
            }
        }
    }

    fun main() {
        Person.hello()      // 靜態函數

        Person.a = 100;

        Person.hello()      // 靜態函數
    }
    ```
    > ![](https://i.imgur.com/MUtNHCg.png)

    :::success
    * **Java 類如何調用 Kotlin 中的 Companion 內容？** 使用 `@JvmField`(屬性), `@JvmStatic`(方法)

        ```kotlin=
        class Companion {
            companion object {

                @JvmField
                var info : String? = null

                @JvmStatic
                fun printInformation() {
                    println(info)
                }

            }
        }
        ```

        Java 類調用如下 （有註解才可以調用）
        ```java=
        public class Java_Main {
            public static void main(String[] args) {
                Companion.info = "123";
                Companion.printInformation();
            }
        }
        ```

        > ![](https://i.imgur.com/MxYixTf.png)
    :::

### 物件聲明 - 單例 object 
* 先來看看 Java 的懶加載實現方式，並考慮到多線程問題

    ```java=
    // Java 的 DCL 單例模式

    public class Singleton {
        private volatile static Singleton instance;
       // 加載覽
        private Singleton() {}

        public synchronized static Singleton getInstance() {
            if(instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    }
    ```

* 在 Kotlin 中加更簡單，只需要**將 class 改為 object** 即可，不須私有化建構函數、也不需要靜態方法、同步；**呼叫單例內的方法是不需使用 `()`**

    > **若是 Singleton 要實例化則會錯誤**

    ```kotlin=
    object Singleton {

        fun print() {
            println("I am Kotlin Singleton");
        }
    }

    fun main() {
        // Singleton()          // Error
        Singleton.print()     // 呼叫不使用 ()
    }
    ```
    > ![](https://i.imgur.com/csKe2WG.png)

    :::success
    * 以下不使用 Kotlin#`object` 關鍵字（Object 類是使用了 static 加載對象），使用 Kotlin 模仿 Java 的懶加載方式

        ```kotlin=
        class MySingleton {

            companion object {
                // 必須使用 private 修飾
                private var instance: MySingleton ?= null

                fun getInstance() : MySingleton {
                    if(instance == null) {
                        instance = MySingleton()
                    }
                   return instance!!    // !! 代表自身負責
                }

                fun printInfo() {
                    println("Hello Singleton")
                }
            }

            fun printInfo() {
                println("Hello Singleton Working")
            }
        }

        fun main() {
            MySingleton.printInfo()
            
            MySingleton.getInstance().printInfo()
        }
        ```
        **--實作結果--**
        > ![](https://i.imgur.com/GCBpJHP.png)
    :::

### 物件表達 - 匿名類

* 這種表達式類似於 Java 中的匿名類，並且可以支持實現多個接口
    
    :::info
    object 它可實現多個方法
    :::

    ```kotlin=
    interface OnClick {
        fun onClick()

        fun onCancel()
    }

    class View constructor(val onClick: OnClick){
        
        fun touch() = onClick.onClick()

        fun cancel() = onClick.onCancel()

    }

    fun main() {
        val view = View(object : OnClick {
            override fun onClick() {
                println("View be click.")
            }

            override fun onCancel() {
                println("View ve cancel")
            }
        })

        view.touch()

        Thread.sleep(1000)

        view.cancel()
    }
    ```

    > ![](https://i.imgur.com/Xu4sqVY.png)


## 數據類

Koltin 有簡化數據類的使用，讓我們不用寫太多程式


### Bean 類 - data
* 先來複習一下 Java 的 Bean 類寫法，必須覆寫二個方法（通常會寫三個），`equals`、`hashcode`、`toString` (下面註釋會解釋)

    ```java=
    public class DataBean {

        private String name;
        private long id;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public long getId() {
            return id;
        }

        public void setId(long id) {
            this.id = id;
        }

        /**
         * 必須重寫否則無法正常使用 hashMap、hashSet
         */
        @Override
        public int hashCode() {
            return name.hashCode() + (int)id;
        }

        /**
         * hashCode & equals 是配套
         * 1. 比較類型
         * 2. 數據內容
         */
        @Override
        public boolean equals(@Nullable Object obj) {
            if(obj instanceof DataBean) {
                DataBean bean = (DataBean) obj;
                return bean.name.equals(this.name) && bean.id == id;
            }
            return false;
        }

        @NonNull
        @Override
        public String toString() {
            return "DataBean= name: " + name + ", id: " + id;
        }
    }
    ```

* Kotlin 更加的簡單，只需要在類前加上 `data` 關鍵字即可，並在主建構函數建立必須的參數（並使用 `val` 字段描述），**它會幫我們把以上三個函數做完**，不需要手動覆寫 (也包括 clone 函數)

    1. equals / hashCode
    
    2. toString 
    
    3. componentN：有多少個屬性 N 就會是多少
    
    4. copy：可以複製全部或是部分屬性

    ```kotlin=
    // Params 必須使用 val 描述
    data class DataBean(val name: String, val id: Long)    // 不可使用大括號 {}

    fun main() {
        val dataBean1 = DataBean("Alien", 9527);
        val dataBean2 = DataBean("Alien", 9527);

        println("dataBean1: $dataBean1")
        println("dataBean1 == dataBea: ${dataBean1 == dataBean2}")
        // println("dataBean1 == dataBea: ${dataBean1.equals(dataBean2)}")    // 同上
    }
    ```

    **--實做結果--**
    > ![](https://i.imgur.com/DmtYb8Q.png)

    :::warning
    * data class 的拷貝函數是 深拷貝 還是 淺拷貝？  **淺拷貝**

        ```kotlin=
        data class PersonInfo(val name: String, val id: Long)

        fun main() {
            val p1 = PersonInfo("Alien", 123)
            println("origin: $p1")

            // 全部複製
            val p1_copy = p1.copy()
            println("copy: $p1_copy")

            // 淺拷貝判斷
            println("origin name === copy name? ${p1.name === p1_copy.name}")

            // 指定複製
            val p2_copy = p1.copy(name = "Kyle")
            println("copy 2: $p2_copy")
        }
        ```

        > ![](https://i.imgur.com/7nuHWhN.png)
    :::


### 密封類 - Sealed



* **密封類**：一般來說 Kotlin 並不會判斷 (使用 when 判斷) 是否是完全符合使用者的設定，若是不符合也不會警告，只會拋向 else，這會導致設計出的程式不構安全，Kotlin 也有解決的辦法

* Sealed 可以讓你寫出更健全的程式，它的功能類似於 Android 的 `@IntDef` 註解，並且功能更強大，**若是 ++判斷沒有實作 Sealed 密封類的判斷編譯器會直接報錯++**

    :::warning
    **Sealed 類是一個抽象類**，子類可以在任意位置
    :::

    先來做一個沒有密封的類，並對其做判斷，會發現 when 必須要使用 else 否則會錯誤 (編譯根本過不了)，儘管不需要也要增加
    ```kotlin=
    interface Result    // 接口

    // 實作 Result 接口 (不須 {})
    class Success(val msg: String) : Result       
    class Failure(val err: Exception) : Result

    fun getResultMeg(result: Result) : String? = when(result) {
        is Success -> result.msg
        is Failure -> result.err.message
        // 必須添加 else，盡管不需要
        else -> throw IllegalArgumentException()    // 不夠密封，會有一些危險拋出
    }
    ```

    再來做一個密封類，**使用 ^1.^ 密封類必須要使用在 class，^2.^ sealed 描述的類不需要使用 open 才能繼承，^3^ 不需使用 abstruct 加以描述**

    ```kotlin=
    sealed class Result2
    // 抽象繼承
    class Success2(val msg: String) : Result2()     // 繼承必須要有括號 (預設建構函數)
    class Failure2(val err: Exception) : Result2()

    fun getResultMeg2(result2: Result2) : String? = when(result2) {
        is Success2 -> result2.msg
        is Failure2 -> result2.err.message
        // 不須使用 else
        // else -> throw IllegalArgumentException()    // 多餘
        
        throw IllegalArgumentException()    // 同上    
    }
    ```

    **--觀察結果 1--**
    > when is exhaustive(詳盡的) so 'else' is redundant(多餘) here
    > 
    > ![](https://i.imgur.com/LcI7v2c.png)


    **--觀察結果 2--**
    > 若是新增了一個 Unknow 就必須在 when 中賦予值 or 使用 else，否則會報錯
    >
    > ![](https://i.imgur.com/kJvYlUU.png)


    
## 再提及靜態

在 Kotlin 中要定義靜態變數、方法都比 Java 較麻煩一些（Java 只需要加 static 關鍵字即可）
> ![](https://i.imgur.com/5ePzR1P.png)


### 單例 - object 
* 透過 object 來描述可以讓該類變成單例類（不須 class 關鍵字），事實上 object 內部它並不是靜態方法，而是透過

    ```kotlin=
    object TestSingle {

        fun say() {
            println("Hello object")
        }
    }

    fun main() {
        // 實際上是調用了 TestSingle.INSTANCE.say()
        TestSingle.say()    
    }
    ```

* 透過工具 `Tools` -> `Kotlin` -> `Show Kotlin ByteCode` -> `Decompile` 可以看到反編譯的程式，內部就是使用餓漢加載的方式

    ```java=
    public final class TestSingle {
       public static final TestSingle INSTANCE;

       public final void say() {
          String var1 = "Hello object";
          boolean var2 = false;
          System.out.println(var1);
       }

       private TestSingle() {
       }

       static {
          TestSingle var0 = new TestSingle();
          INSTANCE = var0;
       }
    }
    ```
    **--實做結果--**
    > **會透過該檔案名稱 +Kt 創建一個新的類**，之後的呼叫方法如同我們預估 （請忽略報錯）
    > 
    > ![](https://i.imgur.com/1Rj4JUL.png)


### 偽靜態 - Companion object
* 全名是 companion object，**必須使用在類內，類之外不可以使用**，並且**實際上它並不是靜態類，而是該類的 ==伴生類==**

    ```kotlin=
    class MyCompanion {

        fun sayHello() {
            println("Hello~ outside class")
        }

        companion object {
            fun sayHello() {
                println("Hello~ companion object")
            }
        }

    }

    fun main() {
        MyCompanion().sayHello()    // normal
        MyCompanion.sayHello()      // companion
    }
    ```
    **--實做結果--**
    > ![](https://i.imgur.com/ZpQm8qS.png)


:::info
* **反編譯 Kotlin 的 Companion 來觀察**

    以下是 byteCode 反編譯過後的程式，可以看到 **companion object 是一個靜態內部類，並且在外部使用 static 直接加載，所以可以調用**，也證明 companion 並不是真正的靜態，它是伴生類
    ```kotlin=
    @Metadata(
       mv = {1, 1, 16},
       bv = {1, 0, 3},
       k = 1,
       d1 = {"\u0000\u0014\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0002\b\u0002\u0018\u0000 \u00052\u00020\u0001:\u0001\u0005B\u0005¢\u0006\u0002\u0010\u0002J\u0006\u0010\u0003\u001a\u00020\u0004¨\u0006\u0006"},
       d2 = {"Lclass_05/MyCompanion;", "", "()V", "sayHello", "", "Companion", "LearnClass1"}
    )
    public final class MyCompanion {
       // 使用 static 直接加載
       public static final MyCompanion.Companion Companion = new MyCompanion.Companion((DefaultConstructorMarker)null);

       public final void sayHello() {
          String var1 = "Hello~ outside class";
          boolean var2 = false;
          System.out.println(var1);
       }

       @Metadata(
          mv = {1, 1, 16},
          bv = {1, 0, 3},
          k = 1,
          d1 = {"\u0000\u0012\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0000\b\u0086\u0003\u0018\u00002\u00020\u0001B\u0007\b\u0002¢\u0006\u0002\u0010\u0002J\u0006\u0010\u0003\u001a\u00020\u0004¨\u0006\u0005"},
          d2 = {"Lclass_05/MyCompanion$Companion;", "", "()V", "sayHello", "", "LearnClass1"}
       )
       // 靜態內部類
       public static final class Companion {
          public final void sayHello() {
             String var1 = "Hello~ companion object";
             boolean var2 = false;
             System.out.println(var1);
          }

          private Companion() {
          }

          // $FF: synthetic method
          public Companion(DefaultConstructorMarker $constructor_marker) {
             this();
          }
       }
    }
    ```
:::


### Kotlin 的真靜態 
* Kotline 有兩種實現真正靜態的方式，^1.^ `@JvmStatic` 註解、^2.^ 頂層方法

    1. **註解使用 ==`@JvmStatic` 註解==：經過編譯過後就會讓這些方法成為真正的靜態方法，並且 ++該註解只能使用在 companion object 內++**

        ```kotlin=
        class TestStatic {

        //    fun sayHello() {        // Err. 會引發衝突 (原因在下方說明)
        //        println("Hello~ outside class")
        //    }

            companion object {

                @JvmStatic        // 只能使用在 companion object 內
                fun sayHello() {
                    println("Hello~ companion object")
                }
            }

        }

        fun main() {
            TestStatic.sayHello()      // companion
        }
        ```
        **--實做結果--**
        > ![](https://i.imgur.com/KTUmHeX.png)
        
        :::danger 
        * 為何與外部同名方法衝突

            反編譯代碼，**可以發現 compation object 仍然在，但是外部產了了相同的函數**，這就是衝突的原因
            
            ```kotlin=
            public final class TestStatic {
               // 還是使用靜態類
               public static final TestStatic.Companion Companion = new TestStatic.Companion((DefaultConstructorMarker)null);

               // 外部產生相同的名稱的函數
               @JvmStatic
               public static final void sayHello() {
                  Companion.sayHello();
               }

               @Metadata(
                  mv = {1, 1, 16},
                  bv = {1, 0, 3},
                  k = 1,
                  d1 = {"\u0000\u0012\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0000\b\u0086\u0003\u0018\u00002\u00020\u0001B\u0007\b\u0002¢\u0006\u0002\u0010\u0002J\b\u0010\u0003\u001a\u00020\u0004H\u0007¨\u0006\u0005"},
                  d2 = {"Lclass_05/TestStatic$Companion;", "", "()V", "sayHello", "", "LearnClass1"}
               )
               public static final class Companion {
                  @JvmStatic
                  public final void sayHello() {
                     String var1 = "Hello~ companion object & JvmStatic";
                     boolean var2 = false;
                     System.out.println(var1);
                  }

                  private Companion() {
                  }

                  // $FF: synthetic method
                  public Companion(DefaultConstructorMarker $constructor_marker) {
                     this();
                  }
               }
            }
            ```
        :::
        

    2. **頂層方法**：會發現 Kotlin 的方法並不需要定義在類 (Class) 內，可以定義在外部，在 **外部 (不在類內) 的方法就是頂層方法**

        ```kotlin=
        /**
         * External.kt 檔案
         */ 
        // 不存在類內's 方法
        fun externalSayHello() {
            println("Hello~ External top function")
        }
        ```
        Kt 會自動依照檔案名稱生成一個，`檔名 + Kt.java` 的 Java 檔案，以下為反編譯結果

        ```java=
        @Metadata(
           mv = {1, 1, 16},
           bv = {1, 0, 3},
           k = 2,
           d1 = {"\u0000\b\n\u0000\n\u0002\u0010\u0002\n\u0000\u001a\u0006\u0010\u0000\u001a\u00020\u0001¨\u0006\u0002"},
           d2 = {"externalSayHello", "", "LearnClass1"}
        )
        public final class ExternalKt {
           public static final void externalSayHello() {
              String var0 = "Hello~ External top function";
              boolean var1 = false;
              System.out.println(var0);
           }
        }
        ```
        使用 java 呼叫
        ```java=
        class CallExternal {

            public static void main(String...s) {
                ExternalKt.externalSayHello();
            }
        }
        ```
        **--實做結果--**
        > ![](https://i.imgur.com/LCxsmAx.png)


## 空指針檢查
在 ++Source 時期++ 的 **空指針檢查是 Kotlin 的一大特色**，排除了大部分的空指針操作，因為以前空指針只能靠程式設計師自己察覺 (狀態不好就會一堆空指針)

```kotlin=
fun main() {
    customShow(null)    // 其實 Kotlin 不允許空傳空，下會會說到
}

class BookShop {

    fun getDescribe() : String {
        return "This is Book Shop"
    }

    fun workTime() {
        println("Open At 9:00 till 21:00")
    }
}

/**
 * 以往都需要在執行前判斷，但這會導致過多不需要的程式 
 * ( 判空看起來不爽，但是出 Null Pointer Exception 會更不爽
 */
fun customShow(s: BookShop) {
    if(s != null) {
        s.getDescribe()
        s.workTime()
    }
}
```

### Kotlin 空類型限制
* **Kotlin 預設是不允許空類型的 (所有的參數、變量)**，若是傳入空類型 (null) 則會提示錯誤，若是不處理則會編譯無法通過

    > ![](https://i.imgur.com/haqVfor.png)

* 若是需要空指針的操作，就必須 **在類名後方加入 ==?==，代表了該變數 ++可為空++**

    > Ex: PrintWord (hello : String ?, time : int ?)，表示這兩個變數可以為空指針，也就是呼叫時可以傳入 null

    ```java=
    fun main() {
        customShow(null)    // 其實 Kotlin 不允許空傳空，下會會說到
    }

    class BookShop {

        fun getDescribe() : String {
            return "This is Book Shop"
        }

        fun workTime() {
            println("Open At 9:00 till 21:00")
        }
    }

    /**
     * 以往都需要在執行前判斷，但這會導致過多不需要的程式
     * ( 判空看起來不爽，但是出 Null Point 會更不爽
     */
    fun customShow(s: BookShop ?) {  // 引數加上 ? 代表了同意為空操作
        s.getDescribe()     // 會警告可能會空指針，也就編譯不過
        s.workTime()
    }
    ```
    **--實作結果--**
    > ![](https://i.imgur.com/3AlKDqo.png)


### 判空輔助符號

| 符號 | 說明 |
| -------- | -------- |
| <對象>==?.==<方法> | 判斷呼叫的對象是否為空，不為空才往下執行呼叫 |
| <表達式>==?:==<表達式> | 左邊表達式判斷若為空，就執行右邊表達式 |
| <對象>==!!== | 該對象是否為空由程序設計者自己判斷，也就是讓盼空系統失效 |

1. **`?.` 判空符號**

    ```java=
    fun isEmpty(s: BookShop?) {
        s?.getDescribe()
        s?.workTime()

        // 相當於下方程式
        if(s != null) {
            s.getDescribe()
        }
        if(s != null) {
            s.workTime()
        }
    }
    ```

2. **`?:` 賦值符號**

    ```kotlin=
    fun decide() : Int {
        val a : Int? = null
        val b : Int = 123

    //    return if(a != null) a else b
        return a ?: b   // 功能同上
    }
    ```

3. **`!!`，非空斷言工具**

    ```kotlin=
    fun notEmpty(s: BookShop?) {
        s!!.getDescribe()
        s!!.workTime()
    }

    /**
     * 返回值 + ? 代表可能返回空
     */
    fun returnNotEmpty() : BookShop? {
        val s : BookShop? = null
        return s!!        // 也就是程式設計師自己負責空指針
    }
    ```
    > ![](https://i.imgur.com/MGLiVDn.png)


## 更多的 Kotlin 語言相關文章

在這裡，我們提供了一系列豐富且深入的 Kotlin 語言相關文章，涵蓋了從基礎到進階的各個方面。讓我們一起來探索這些精彩內容！

### Kotlin 語言基礎

* **Kotlin 語言基礎**：想要建立堅實的 Kotlin 基礎？以下這些文章將帶你深入探索 Kotlin 的關鍵基礎和概念，幫你打造更堅固的 Kotlin 語言基礎

    :::info
    * [**Kotlin 函數、類、屬性 | DataClass、Sealed、Object 關鍵字 | Enum、Companion、NPE**](https://devtechascendancy.com/kotlin-functions_oop_dataclass_sealed_object/)
        
    * [**深入探究 Kotlin 與 Java 泛型：擦除、取得泛型類型、型變、投影 | 協變、逆變**](https://devtechascendancy.com/explore-kotlin-java-generics_type_erasure/)

    
    * [**深入 Kotlin 函數特性：Inline、擴展、標準函數全解析 | 提升程式碼效能與可讀性**](https://devtechascendancy.com/kotlin_inline_extensions_standards-func/)

    * [**Kotlin DSL、操作符、中綴表達式 Infix | DSL 詳解 | DSL 設計與應用**](https://devtechascendancy.com/kotlin-dsl-operators-infix-explained/)
    :::

### Kotlin 特性、特點

* **Kotlin 特性、特點**：探索 Kotlin 的獨特特性和功能，加深對 Kotlin 語言的理解，並增強對於語言特性的應用

    :::warning
    * [**Kotlin 代理與懶加載機制：使用、lazy 深度解析**](https://devtechascendancy.com/kotlin-delegate_java-proxy_lateinit_lazy/)
    
    * [**Kotlin Lambda 編程 & Bytecode | Array & Collections 集合 | 集合函數式 API**](https://devtechascendancy.com/kotlin-lambda-bytecode-array-collections-functional/)
    
    * [**深入理解 Kotlin：智能推斷與 Contact 規則**](https://devtechascendancy.com/kotlin-smart-inference-contract-rules-guide/)
    :::

### Kotlin 進階：協程、響應式、異步

* **Kotlin 進階：協程、響應式、異步**：若想深入學習 Kotlin 的進階主題，包括協程應用、Channel 使用、以及 Flow 的探索，請查看以下文章

    :::danger

    * [**應用 Kotlin 協程：對比 Thread、創建協程、任務掛起 | Dispatcher、CoroutineContext、CoroutineScope**](https://devtechascendancy.com/applied-kotlin-coroutines-in-depth-guide/)
    
    * [**Kotlin Channel 使用介紹 | Select、Actor | 生產者消費者**](https://devtechascendancy.com/kotlin-channel_select_actor_cs/)

    * [**探索 Kotlin Flow：基本使用、RxJava 對比、背壓機制 | Flow 細節**](https://devtechascendancy.com/kotlin-flow-usage_compare-rx_backpressure/)
    :::




## Appendix & FAQ

:::info
:::

###### tags: `Kotlin`
