---
# System prepended metadata

title: Kotlin 泛型、類與類型 - 協變、逆變
tags: [Kotlin]

---

---
title: 'Kotlin 泛型'
disqus: kyleAlien
---

Kotlin 泛型
===

## Overview of Content

以下參考，Kotlin 進階實戰

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

Java 泛型可以參考 [**Java 泛型篇**](https://hackmd.io/Dlm0Ov0URya2t6mbTgNQkQ?view#Java-%E6%B3%9B%E5%9E%8B) 


:::success

* 如果喜歡讀更好看一點的網頁版本，可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/)
    
    本篇文章對應的是 [**深入探究 Kotlin 與 Java 泛型：擦除、取得泛型類型、型變、投影 | 協變、逆變**](https://devtechascendancy.com/explore-kotlin-java-generics_type_erasure/)
:::


[TOC]

## Java 泛型擦除

Java 的泛型是 **偽泛型**，這是 Java 因為兼容性的考慮，**泛型只會存在於 `.java` 階段**，在進入 JVM 之前會將泛型轉為 Object 類（也就是在 JVM 運行中，是不會有泛型的）

:::success
**Kotlin 同樣使用泛型擦除機制**，所以以下舉例的 Java 例子也可以使用在 Kotlin 中
:::

### Java 泛型：擦除

* 當泛型指定為基礎類別（`Integer`, `Long`, `String`... 等等），在編譯成 `.class` 之後就會被擦除，指定的泛型類別會轉為 Object 類（這個特點）；範例如下…

    ```java=
    public class JavaGeneral_1 {

        public static void main(String[] args) {
            // 指定泛型類為 String
            List<String> list1 = new ArrayList<>();
            list1.add("Alien");

            // 指定泛型類為 Integer
            List<Integer> list2 = new ArrayList<>();
            list2.add(123);

            System.out.println(list1.getClass());
            System.out.println(list2.getClass());
        }

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

    透過反編譯後，我們可以看到 `List`#`add` 方法的接收參數轉為 Object，而不是在源碼中的泛型指定類
    > ![](https://i.imgur.com/BZoqbzj.png)


### Java 數組類型：不擦除

* 我們看另外一個擦除與否的案例「**數組**, `Array`」；以結論來講 Java 在編譯時 **不會將數組類型擦除**，也就是數組類型會保留到運行時！

    :::warning
    * Java 不支援泛型數組，所以要使用原生類型的數組

        > ![](https://i.imgur.com/hok430n.png)
    :::
    
    Java 數組類型範例如下

    ```java=
    public class JavaGeneral_2 {

        public static void main(String[] args) {
            String[] strArray = new String[10];
            Integer[] intArray = new Integer[10];

            System.out.println(strArray.getClass());
            System.out.println(intArray.getClass());

        }

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


    我們將編譯過後的 `.class` 檔反編譯，會發現 Java 簽名會呈現為 Array 的形式，並不會擦除（仍保留指定類型）

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

### JVM 識別泛型：Class 常量池

* 前我們知道在經過編譯後 Java 會將泛型擦除（轉為 Object 類），那 JVM 如何知道該 Object 類指定的泛型是哪個呢？明明已經被擦除了呀？

    Java 在運行時使用泛型時已經被擦除，但編譯完畢的 `.class` 文件中還是會保存了泛型相關的訊息 （**保存在 class 的常量池中**）

    :::info
    * 使用泛型的程式在編譯後會產生簽名 (`signature`) 字段，**簽名會指向在常量池中實際的類型，之後就可以透過該類型強制轉型**
    :::

## Kotlin 取得泛型


### Java/Kotlin 類：Class 獲得具體泛型

* 不管是匿名類，或是實體類都可以透過 Class 物件來獲取泛型的資訊

    | 取得泛型資訊的函數 | 簡介 |
    | - | - |
    | Class#getGenericSuperclass | 取得類的類型（包含泛型資訊） |
    | ParameterizedType#getActualTypeArguments | ParameterizedType 是 Type 的子類，表示 **參數的類型** (可取得泛型的具體類型) |
    
    1. **Java 匿名類取得泛型資訊**：範例如下

        ```java=
        class Generic_1 {

            private static class GenericClz<T, R> { }

            public static void main(String[] args) {
                // 匿名類
                GenericClz<Integer, String> genericClz = new GenericClz<>() { };

                // 取得類的泛型的類型
                Type genericSuperclass = genericClz.getClass().getGenericSuperclass();
                System.out.println("Generic class: " + genericSuperclass);

                if(genericSuperclass instanceof ParameterizedType) {
                    // 轉譯為 ParameterizedType
                    
                    // 取得實際的泛型類型
                    Type[] actualTypeArguments = ((ParameterizedType) genericSuperclass).getActualTypeArguments();

                    for(Type t: actualTypeArguments) {
                        // 取得泛型類型 Generic type
                        System.out.println("actualTypeArguments class: " + t);
                    }
                }
            }
        }
        ```
        > ![](https://i.imgur.com/79TdahX.png)

        **使用 Kotlin 匿名類，也可以達到同樣的效果**，範例如下
        
        ```kotlin=
        object Generic_1_kt {
            open class GenericClz<T, S>

            @JvmStatic
            fun main(args: Array<String>) {
                // 匿名類
                val genericClz = object : GenericClz<Int, String>() { }

                val typeClz = genericClz.javaClass.genericSuperclass
                println(typeClz)

                when(typeClz) {
                    is ParameterizedType -> {
                        for (i in typeClz.actualTypeArguments) {
                            println(i)
                        }
                    }
                }
            }

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

        
    2. **Java 繼承泛型**：當有一個類繼承於泛型類，並且有指定泛型類型時，也可以透過相同的 API 來取得泛型資訊；範例如下

        ```java=
        class Generic_2 {

            // 基礎泛型
            private static class GenericClz_2<T, R> { }

            // 繼承泛型
            private static class ChildGeneric extends GenericClz_2<String, Long> { }

            public static void main(String[] args) {
                ChildGeneric genericClz = new ChildGeneric();

                Type genericSuperclass = genericClz.getClass().getGenericSuperclass();
                System.out.println("Generic class: " + genericSuperclass);

                if(genericSuperclass instanceof ParameterizedType) {
                    Type[] actualTypeArguments = ((ParameterizedType) genericSuperclass).getActualTypeArguments();

                    for(Type t: actualTypeArguments) {
                        // 取得 Generic type
                        System.out.println("actualTypeArguments class: " + t);
                    }
                }
            }
        }
        ```
        :::info
        * 這也就證明了 Class 類會保存泛型的相關資訊
        :::
        > ![](https://i.imgur.com/lXqTKKC.png)

        **使用 Kotlin 也可以達到同樣的效果**，範例如下

        ```kotlin=
        object Generic_2_kt {
            // 基礎泛型
            open class GenericClz_2<T, S>

            // 繼承泛型
            class ChildGenericClz : GenericClz_2<String, Long>()

            @JvmStatic
            fun main(args: Array<String>) {
                val genericClz = ChildGenericClz()

                val typeClz = genericClz.javaClass.genericSuperclass
                println(typeClz)

                when(typeClz) {
                    is ParameterizedType -> {
                        for (i in typeClz.actualTypeArguments) {
                            println(i)
                        }
                    }
                }
            }

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

### Kotlin 泛型數組

* 我們知道 Java 不支援泛型數組（上面小節有說到），不過 **Kotlin 支持泛性數組！！**

    ```kotlin=
    fun main() {
        // 創建數組, java/kotlin 都保留簽名
        val array1 = arrayOf(1, 2, 3, 4, 5)
        val array2 = arrayOf("A", "B", "C", "D", "E")
        println(array1.javaClass)
        println(array2.javaClass)


        // 創建泛型數組, java 不能這樣寫！不過 kotlin 可以！
        val list1 = listOf<Int>(1, 2, 3, 4, 5)
        val list2 = listOf<String>("A", "B", "C", "D", "E")
        println(list1.javaClass)
        println(list2.javaClass)

    }
    ```
    如同 Java，數組會保持簽名
    > ![](https://i.imgur.com/AKbKonT.png)

    :::info
    * 從上面可以發現 `arrayOf` 沒有擦除, 而 `listOf` 被擦除，而它們有以下差異
        
        | 函數 | 是否使用 `inline` | 是否使用 `reified` (具體化) |
        | - | - | - |
        | arrayOf | Ｙ | Ｙ | 
        | listOf | Ｎ | Ｎ | 
        
        可以看到 `arrayOf` 有使用 `inline`、`reified` 關鍵字，而這兩個關鍵字就可以用來保留泛型類型（以下小節會說明這兩個關鍵字）
        
        ```kotlin=
        // arrayOf 原型
        public inline fun <reified @PureReifiable T> arrayOf(vararg elements: T)
            : Array<T>

        // listOf 原型
        public fun <T> listOf(vararg elements: T): List<T> = 
            if (elements.size > 0) elements.asList() else emptyList()
        ```
    :::


### Kotlin 泛型方法：inline / reified 關鍵字

* 如果要 **保留類型不被擦除就需要使用 `inline` + `reified` 描述**，這樣才能 **不透過實例（`instance`）** 直接取得泛型 Class！


    ```kotlin=
    inline fun <reified T : Any> Gson.formJson(jsonStr: String) : T =
        // 這樣才能直接取得泛型 Class
        Gson().formJson(json, T::class.java)
    ```

* 再一個 `inline` + `reified` 的使用範例，加強印象

    ```kotlin=
    class Test {

        inline fun <reified T> toClassArray(vararg elements: T) : List<Class<*>> {
            val res = mutableListOf<Class<*>>()

            // 直接取得泛型 T 的實際類型
            val clz = T::class.java

            for (i in elements) {
                res.add(i!!::class.java)
            }

            return res.toList()
        }
    }

    fun main() {
        val list = Test().toClassArray<Int>(1, 2, 3, 4)
        
        for(i in list) {
            println(i)
        }
    }
    ```

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

    :::warning
    
    * **沒有 `reified` 就無法透過 泛型 取得 class**
        
        > ![](https://i.imgur.com/gVMDBBV.png)
    :::

## 通配符：型變

通配符以 Java 來說就是 `?` 符號，可以用在讓 **泛型產生繼承關係** 的指定，加強靜態語言的檢查特性

> Kotlin 中是指 ++類型++ 轉換後的繼承關係


### 類 Class ＆ 類型 Type

* **在 Kotlin 中，==類 ＆ 類型是兩種不同的概念==**（或是說它強調了兩者的不同概念）

    * **類型（`Type`）**：具有相同特性的物件，強調了「**特性**」的概念，在程式中的表達方式通常可以使用 `interface`、`abstract` 描述特性

    * **類（`Class`）**：類是類型的實現，提供「**具體**」的數據結構、方法集合，提供共同方法、操作，**它說明了如何使用物件**

        :::warning
        * **所以子類（`SubClass`） & 子類型（`SubType`）有很大的區別**

            所有出現 Type 的地方可被另一個 SubType 取代；它們有相同的特性。在程式中，任何出現類型的地方都可以被另一個子類型取代

            > 以 Java 泛型來舉例：`List<? extends Hello>`，可使用取代 `?` 符號的類型都是 Hello 或是 Hello 的子類
        :::

* **接著我們再來看 Kotlin 的「可空類型, `Nullable Type`」與類之間的關係**
    
    Kotlin 的「可空類型」代表的是一種類型（`Type`），它說明的是該類型是「**可空的特性**」，可空類型本身並不代表一個具體的類，而是類型的一種特性，但這個 **可空類型並非代表一個類**！！如下表所示
    
    |       ＼      | Class 類 | Type 類型 |
    | ------------- |:-------- |:--------- |
    | String        | Y        | Y         |
    | String?       | N        | Y         |
    | List          | Y        | Y         |
    | List<String\> | N        | Y         |


### Kotlin 泛型通配符：`Covariance` 協變 out

* 先來看看 Java 的「**通配符上限, `extends`**」；**如果沒有通配符限制，每個類型都是單獨個體**，不會有任何關係
    
    ```java=
    public class JavaType {

        // 父類
        private static class Animal { }

        // 子類
        private static class Dog extends Animal { }

        public static void main(String[] args) {
            List<Animal> animalList = new ArrayList<>();
            List<Dog> dogList = new ArrayList<>();

            // Error
            animalList = dogList;
        }

    }
    ```
    如下圖，我們可以看到，**==類有關係（上面案例中是繼承關係）不代表類型有關係==**！

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

    要讓「**類 ＆ 類型**」產生關係就要使用 Java 的通配符 `?`，這樣就可以建立「類」與「類型」的產生關係
    
    ```java=
    public class JavaType {

        // 父類
        private static class Animal { }

        // 子類
        private static class Dog extends Animal { }

        public static void main(String[] args) {
            
            // 使用通配符！
            List<? extends Animal> animalList = new ArrayList<>();
            List<Dog> dogList = new ArrayList<>();
            // OK
            animalList = dogList;
        }

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


* **Kotlin 中實現同樣的效果則是使用 `out` 關鍵字**；像是 `List<out E>` 就有使用到 `out` 關鍵字
    
    Kotlin 的 List 源碼：可以看到 `<out E>` 關鍵字，它與 Java 的 `<? extends E>` 有相同的功能，它代表了可以 **安全取值**！
    
    ```kotlin=
    // List 源碼
    public interface List<out E> : Collection<E> {
        // Query Operations

        override val size: Int
        override fun isEmpty(): Boolean
        override fun contains(element: @UnsafeVariance E): Boolean
        override fun iterator(): Iterator<E>

        // Bulk Operations
        override fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean
        
        public operator fun get(index: Int): E

        public fun indexOf(element: @UnsafeVariance E): Int
        
        ... 省略部分
    }
    ```
    :::warning
    * **`@UnsafeVariance` 是幹嘛的**？
        
        主要因為這個操作可能違反規則，**用來告訴編譯器這個操作由自己負責**
        
        > 違反規則的操作：像是我可以在 `contains` 函數中修改原來元素的內容，這就不安全
    :::


    同上面 Java 的程式，但我們現在用 Kotlin 來撰寫，可以看到編譯結果是完全正常不會報錯的！
    ```kotlin=
    fun main() {

        open class Animal

        class Dog : Animal()

        // 由於協變
        // 所以 List<Animal> 相當於寫了 List<? extends Animal>
        var animalList : List<Animal> = ArrayList()
        val dogList = ArrayList<Dog>()

        // Okay, Pass ~
        animalList = dogList
    }
    ```
    
    這是因為在 Kotlin 中，對於擁有不同類型參數的泛型類型，如果 **泛型參數的類型是協變的（`out`）**，**則泛型類型本身也是協變的**
    
    > 換句話說 **`List<Dog>` 是 `List<Animal>` 的子類型，因為 List 在 Kotlin 中被聲明為 `<out E>` 的形式**，這也就是為什麼我們可以將 dogList 分配給 animalList，而不需要使用通配符
    
    ```kotlin=
    // 由於協變的關係，Kotlin 會讓 List<Animal>、 List<Dog> 自動產生關係
    List<Animal>

    List<Dog>
    ```
    
    :::success
    * **協變（`Covariance`）結論**：

        如果 A 是 B 的子類（Class 關係），則在 Kotlin 中，協變使用 `out` 關鍵字表示泛型；這意味著，將 `List<A>` 視為 `List<B>` 的子類型（Type 關係）是合法的
        
    :::
    
    
### Kotlin 泛型通配符：`Contravariance` 逆變 in 

* 一樣先來看看 Java 的「**通配符下限, `super`**」；如果沒有通配符限制，則每個類型都是單獨個體，不會有任何關係；我們先來看看 Java 範例…

    ```java=
    class JavaType_2 {

        private static class Animal { }

        private static class Dog extends Animal { }

        public static void main(String[] args) {
            List<? extends Animal> animalList = new ArrayList<>();

            // 無法安全的設定
            animalList.add(new Dog());     // Error

        }

    }
    ```
    為甚麽 `add` 會被編譯器警告？這是 **因為不知道加入 `AnimalList` 的具體子類型，所以無法加入到 List 列表**

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

    使用 `<? surper E>` 修正就可正常設定進列表；**這保證了設定進列表中的元素一定是 `E` or `E 的父類`！** 這相當於 OOP 類設計原則中的 **里式原則的一個展現**
    ```java=
    class JavaType_2 {

        private static class Animal { }

        private static class Dog extends Animal { }

        public static void main(String[] args) {

            // 修正
            List<? super Animal> animalList = new ArrayList<>();

            animalList.add(new Dog());    // Ok
        }

    }
    ```
    > ![](https://i.imgur.com/GyUAVTb.png)
    
    :::info
    * 從這邊也可以看出 `<? extends>`、`<? super>` 是相互對應（正交）關係
        
        * `<? extends>` 用來安全取值
        * `<? super>` 用來安全的設定值
    :::
    
* Kotlin 也可以使用 `in` 關鍵字達到上述 `super` 的效果

    ```kotlin=
    interface IPrint<in E> {
        fun print(e: E)
    }

    class AddInfoPrint : IPrint<String> {
        override fun print(e: String) {
            println("Hello: $e")
        }
    }

    class CountPrint : IPrint<Int> {
        override fun print(e: Int) {
            for (i in 0 until e) {
                println("times: ${i + 1}")
            }
        }
    }

    fun main() {

        val info = AddInfoPrint()
        info.print("123")

        val count = CountPrint()
        count.print(3)
    }
    ```
    > ![](https://i.imgur.com/ygryQiy.png)

    :::danger
    * 如果改成 `out` 就不能安全取值，編譯就會出錯

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


## Kotlin 泛型約束

上面我們提到的是泛型的通配符（主要是讓類型 Type 產生繼承關係），而這裡要講的則是泛型的 **約束**

Java 中使用 `<T extends E>` 來約束能接收的參數只能是 E 或 E 的子類；**在 Kotlin 中使用 `:` 符號來進行約束**

:::warning
* 不能使用 super 限制，只能使用 extends

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


### Kotlin 泛型限制範例

* Kotlin 使用 `:` 符號限制傳入的類型（就像是 `extends`）

    ```kotlin=
    fun <T : Number> sum(vararg param: T) = param.sumOf {
        it.toDouble()
    }

    fun main() {
        val res = sum(1, 2, 3, 0.1) // 所有傳入的參數都是 Number 的子類
        println(res)
    }
    ```
    > ![](https://i.imgur.com/2fux3d2.png)

## Kotlin 投影

投影是 Kotlin 特殊的概念，可以用來 **限制泛型類型**，而不影響原先的類型！


### 限制類型：類型投影

* Kotlin 的 `MutalbleList<T>` 是泛型類，並且沒有用通配符（`<in>`、`<out>`）限制；這時我們可以透過對泛型 `<T>` 限制來達到泛型限制

    ```kotlin=
    fun main() {
        val list1 : MutableList<String> = mutableListOf()
        list1.add("Hello")
        list1.add("World")

        // 可安全的取值
        val list2 : MutableList<out String> = mutableListOf()
        list2.add("111")        // Error
        list2.add("831")        // Error

        // 可安全的設定值
        val list3 : MutableList<in String> = mutableListOf()
        list3.add("555")
        list3.add("666")

        lateinit var list4 : MutableList<String>
        list4 = list3       // Error

    }
    ```
    
    1. 由於 `list2` 使用 `<out>` 來描述，所以不可以設定值
    
    2. 進行投影過限制過的屬性不等於原先類型

        :::info
        * 投影時必須直接指定類型，不可寫在指定屬性時
        
            > ![](https://i.imgur.com/kU4SMwR.png)
        :::


### 限制類型：星號投影

* 如果我們要在 Java 的通配符中不指定任何類型，我們就會使用 `<?>` 符號來表示，用來接收任何類型的泛型；範例如下

    ```java=
    public class Java_Main {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            list.add("AAA");
            list.add("BBB");
            list.add("CCC");
            showList(list);

            List<Integer> list2 = new ArrayList<>();
            list2.add(111);
            list2.add(222);
            list2.add(333);
            showList(list2);
        }

        // 不限定類型
        private static void showList(List<?> list) {
            for (Object o : list) {
                System.out.println(o);
            }
        }
    }
    ```
    > ![](https://i.imgur.com/FdU1xDb.png)

* Kotlin 中使用 `<*>` 符號來取代 `<?>` 符號

    ```kotlin=
    fun showList_1(list: MutableList<*>) {
        for (i in list) {
            print(i)
        }
        println()
    }

    // `<*>` 意思也等同於 `<out Any?>`
    fun showList_2(list: MutableList<out Any?>) {
        for (i in list) {
            print(i)
        }
        println()
    }

    fun main() {
        val intList = mutableListOf<Int>(1, 2, 3, 4, 5)
        val strList = mutableListOf<String>("A", "B", "C", "D", "E")

        showList_1(intList)
        showList_1(strList)

        showList_2(intList)
        showList_2(strList)
    }
    ```
    > ![](https://i.imgur.com/BryysFX.png)
    
    :::danger
    像是這種不限定類型的投影，**只可以用來取得，不可用在寫入** (`out` 跟 `extends` 的概念類似)
    :::


## 更多的 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`
