# 【kotlin】因為需要 < reified Ｔ> 取得泛型型別，所以從 inline functions 開始

參考:   
[https://kotlinlang.org/docs/inline-functions.html](https://kotlinlang.org/docs/inline-functions.html)  
[泛型基礎 (三) — Java 與 Kotlin 向下相容、Type Erasure 和 Reifiable](https://medium.com/kuos-funhouse/%E6%B3%9B%E5%9E%8B%E5%9F%BA%E7%A4%8E-%E4%B8%89-java-%E8%88%87-kotlin-%E5%90%91%E4%B8%8B%E7%9B%B8%E5%AE%B9-type-erasure-%E5%92%8C-riefiable-f8a3160510f5)  
[Inline functions - Kotlin Vocabulary](https://www.youtube.com/watch?v=wAQCs8-a6mg)  
[Kotlin 的 inline、noinline、crossline function](https://medium.com/evan-android-note/kotlin-%E7%9A%84-inline-noinline-crossline-function-8deceacc893d)

### **從Type Erasure 開始**

型別擦除（Type Erasure）是 JVM 泛型的一個特性，在執行時會移除泛型型別資訊。這意味著在編譯後，所有的泛型型別資訊都會被擦除，導致在運行時無法直接訪問或判斷泛型的具體型別。

### 型別擦除的原因

型別擦除的主要原因是為了保持向後兼容性。Java 在設計泛型時，需要確保編譯後的程式碼能夠在不支持泛型的 JVM 上運行。通過在編譯時檢查型別並在運行時擦除泛型資訊，Java 能夠確保新舊版本的兼容性。

### 型別擦除的影響

型別擦除會導致一些限制和問題，包括：

**1.運行時無法獲取泛型資訊**：

- - 由於型別資訊在運行時被擦除，無法在運行時獲取泛型的具體型別。例如：

```Java
fun <T> checkType(obj: T) {
    val button = "Btn String"
    if (button is T) {					// 編譯會失敗
        println("it's String")
    }
}
```

[![image-1721292324617.png](https://bookstack.treemanou.com/uploads/images/gallery/2024-07/scaled-1680-/QBMBAy6hp22dfppb-image-1721292324617.png)](https://bookstack.treemanou.com/uploads/images/gallery/2024-07/QBMBAy6hp22dfppb-image-1721292324617.png)

**2.型別轉換的警告**：

- 在編譯時會產生未經檢查的型別轉換警告，因為編譯器無法完全確保型別的安全性。

```JavaScript
val rawList: List<*> = ArrayList<Any>()
val stringList: List<String> = rawList as List<String>  // 未經檢查的型別轉換警告

```

**3.創建泛型型別的實例**：

- 由於運行時無法確定泛型的具體型別，無法直接創建泛型型別的實例。

```Java
class MyClass<T> {
    fun createInstance(): T {
        return T()  // 錯誤，無法創建泛型型別的實例
    }
}
```

### 型別擦除的工作原理

型別擦除的工作原理如下：

**替換泛型參數**：

- 在編譯時，編譯器會用它們的邊界（上限）替換泛型參數。如果沒有明確的邊界，則使用 `Object` 作為默認邊界。例如，`List<T>` 會被替換為 `List<Object>`。

**插入型別檢查和轉換**：

- 在編譯後的程式碼中插入型別檢查和轉換，以確保在運行時的型別安全。例如：

```Java
List<String> stringList = new ArrayList<>();
stringList.add("Hello");
String str = stringList.get(0);  // 編譯後，將插入型別轉換 (String)

```

### 克服型別擦除的方法

在某些情況下，可以使用以下方法克服型別擦除的限制：

**傳遞型別參數**：

- 可以通過傳遞 `Class<T>` 或 `TypeToken<T>` 來保留型別資訊。

```Java
public <T> T createInstance(Class<T> clazz) throws InstantiationException, IllegalAccessException {
    return clazz.newInstance();
}

```

**使用反射**：

- - 使用反射可以在運行時獲取一些泛型資訊，儘管這可能會變得複雜且難以維護。

```Java
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type

// 定義一個泛型類
class GenericClass<T>

// 繼承泛型類並指定具體的泛型型別
class StringGenericClass : GenericClass<String>()

fun main() {
    // 創建一個 StringGenericClass 的實例
    val instance = StringGenericClass()

    // 獲取泛型類的超類型（即 GenericClass<String>）
    val superclass: Type = instance::class.java.genericSuperclass

    if (superclass is ParameterizedType) {
        // 獲取泛型參數的型別（即 String）
        val actualTypeArgument = superclass.actualTypeArguments[0]
        println("泛型參數的型別是：${actualTypeArgument.typeName}")
    } else {
        println("無法獲取泛型參數的型別")
    }
}

```

<span style="background-color: #ffff00;">**Kotlin 的 `reified` 型別參數**</span>：

- - 在 Kotlin 中，使用 `reified` 關鍵字來保留泛型型別資訊，以便在運行時可以訪問這些資訊。

---

### **使用 reidied 必須使用 inline function** 

所以...

---

### **inline function**

在 Kotlin 中，使用 `inline` 修飾符可以將<span style="background-color: #ffff00;">函式的內容插入到每一個函式呼叫的地方</span>，而不是像普通函式那樣進行函式調用。這樣做可以減少函式調用帶來的開銷，特別是當函式體非常簡單且多次調用時，有助於提升程式的執行效能。

```Java
// 原本的function 在呼叫到hello() 會去hello 執行內部 println 動作
fun main(args: Array<String>) {
    hello()
}

fun hello() {
    println("Hello World !!")
}

/**
 * inline functino (加了inline)
 * 編譯時會將hello 內容插入呼叫位置
 */
fun main(args: Array<String>) {
   println("Hello World !!") // <- 將hello 內容插入 
}

inline fun hello() {
    println("Hello World !!")
}


```

也可以使用 lambda

```Java
fun main(args: Array<String>) {
    todo{
        println("hello world")
    }
}

inline fun todo(doSomeThing:()->Unit) {
    doSomeThing()
}
```

以下是使用 `inline` 修飾符的一些特點和注意事項：

1. **函式內容插入**：被標記為 `inline` 的函式在被調用時，其<span style="background-color: #ffff00;">函式體內的程式碼會被直接插入到呼叫處</span>，而不是進行常規的函式調用。
2. **減少函式調用開銷**：這樣做可以減少函式調用帶來的堆疊和參數傳遞開銷，特別是對於一些簡單的函式，可以有效提升執行效能。
3. **限制和注意事項**：由於內聯函式將函式體內容插入到每一個呼叫點，所以函式體內不能使用非公共 API 的聲明（如私有或內部聲明）。這是為了避免在模組之間的二進制不兼容性問題。

總結來說，Kotlin 中的內聯函式是一種提高執行效能的機制，特別適合於一些較短和簡單的函式，可以有效地減少函式調用的開銷。

有時你需要訪問作為參數傳遞的型別：

```Java
fun <T> TreeNode.findParentOfType(clazz: Class<T>): T? {
    var p = parent
    while (p != null && !clazz.isInstance(p)) {
        p = p.parent
    }
    // 沒有reified 會發出警告用下面這行消除
    @Suppress("UNCHECKED_CAST")
    return p as T?
}
```

```JavaScript
// 所以會長得像這樣
treeNode.findParentOfType(MyTreeNode::class.java)
```

更好的解決方案是直接將型別作為參數傳遞給這個函式。你可以這樣調用它：

```JavaScript
treeNode.findParentOfType<MyTreeNode>()
```

為了實現這一點，內聯函式支持具現化（reified）型別參數，因此你可以這樣寫：

```Ruby
inline fun <reified T> TreeNode.findParentOfType(): T? {
    var p = parent
    while (p != null && p !is T) {
        p = p.parent
    }
    return p as T?
}
```

```Java
inline fun <reified T> example(value: T) {
    // 在函式內部可以使用 T 的真實類型信息
    println("Type of value is: ${T::class.simpleName}")
}

fun main() {
    example(42)  // 調用時傳入 Int
    example("Hello")  // 調用時傳入 String
}

// Type of value is: Int
// Type of value is: String
```

```Ruby
private inline fun <reified T : BaseEntity> gsonProcessAndSaveMongodb(
            value: String,
            repo: MongoRepository<T, *>
    ): T {
        val data: T = gson.fromJson(value, T::class.java)
        logger.info("{} data is {}", T::class.simpleName, data)
        repo.save(data)
        return data
    }
////////////////////////////////////////////////////////////////
val topic: String = record.topic()
val key: String = removeRandomKey(record.key())
val value: String = record.value()
when (key) {
  APP_LIVE_USER -> {
    gsonProcessAndSaveMongodb(value, appLiveUserRepo)
  }

  APP_LIVE_PAGE_VIEW -> {
    gsonProcessAndSaveMongodb(value, appLivePageViewRepo)
  }
 .....
}
```

#### 特點和優點

使用 `reified` 型別參數有幾個主要優點：

- **運行時類型資訊**：通過 `reified`，函式內部可以獲取到泛型型別的實際類型資訊，例如使用 `T::class` 可以獲取到 `T` 的 `KClass` 對象。
- **簡化語法**：不需要額外的類型轉換或者反射操作，使得程式碼更加清晰和簡潔。
- **效能**：因為函式是內聯的，且使用了 `reified` 型別，編譯器可以在編譯時期進行更多的優化，而無需在運行時進行額外的操作。

#### 注意事項

使用 `reified` 型別參數有一些限制和注意事項：

- 只能用於<span style="background-color: #ffff00;">內聯函式</span>中，因為它需要編譯器能夠直接插入泛型實體化的程式碼。
- 只能在函式內部使用 `T::class` 或類似的語法來獲取泛型型別的類型資訊，<span style="background-color: #ffff00;">無法在函式外部</span>進行操作。
- `reified` 型別參數<span style="background-color: #ffff00;">僅限於函式內部</span>，不影響函式的參數或返回類型的宣告。

總結來說，`reified` 型別參數是 Kotlin 中一個強大的功能，用於在泛型程式碼中取得運行時類型資訊，提升了程式碼的靈活性和效能。

---

### **noinline** 

如果有多個參數，不想被inline 可以使用關鍵字 noinline

```Java
fun main(args: Array<String>) {
    todo({
        println("hello world")
    },{
        println("hello world2")
    })
}

inline fun todo(
    doSomeThing:()->Unit,
    noinline doSomeThing2:()->Unit
) {
    doSomeThing()
    doSomeThing2()
}
```

### **crossinline**

inline functtion 使用 return 會有意想不到的狀況，可使用 crossinline 強迫使用 return@XXX 退出

```Java
fun main(args: Array<String>) {
    todo({
        println("hello world")
        return // 因為使用inline，return 後以下不會執行
    },{
        println("hello world2")
    })
}

inline fun todo(
    doSomeThing:()->Unit,
    doSomeThing2:()->Unit
) {
    doSomeThing()
    doSomeThing2()
}

// hello world

```

```Java
fun main(args: Array<String>) {
    todo({
        println("hello world")
        return@todo	// 使用crossinline 會強迫使用 return@{function name} 跳出
    },{
        println("hello world2")
    })
}

inline fun todo(
    crossinline doSomeThing:()->Unit,
    doSomeThing2:()->Unit
) {
    doSomeThing()
    doSomeThing2()
}
// hello world
// hello world2
```

---

### **Scope function 就是使用 inline function**

`@kotlin.internal.InlineOnly`

表示這個函式是一個內聯函式，但它只能在 Kotlin 標準函式庫內部使用。這個註解通常用於指示編譯器只將這個函式內聯化，而不生成實際的函式調用。

#### **let**

```Java
val str: String? = "Hello"   
//processNonNullString(str)       // compilation error: str can be null
val length = str?.let { 
    println("let() called on $it")   
    it.length
}
 println(length) // 5
```

**`inline fun <T, R> T.let(block: (T) -> R): R` 函式定義**：

- `inline fun`：這表示 `let` 函式是一個內聯函式，其函式體會被內聯到每一個函式調用處。
- `<T, R>`：這是泛型參數列表。`T` 表示函式調用的對象的類型，而 `R` 表示 `block` 函式的返回類型。
- `T.let(block: (T) -> R)`：這是函式的簽名。它擁有一個 `block` 參數，這個 `block` 是一個接受 `T` 類型參數並返回 `R` 類型的函數。
- `: R`：函式的返回類型是 `R`，即 `block` 函式的返回值類型。

```Java
/**
 * Calls the specified function [block] with `this` value as its argument and returns its result.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#let).
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}
```

---

#### **apply**

```Java
 val john = Person("John") .apply {
   age = 18
   birthplace = "Taipei"
 }
 println(john) // name:John, age:18, birthplace:Taipei
```

**`inline fun <T> T.apply(block: T.() -> Unit): T` 函式定義**：

- `inline fun`：這表示 `apply` 函式是一個內聯函式，其函式體會被內聯到每一個函式調用處。
- `<T>`：這是一個泛型參數，表示函式可以操作任意類型 `T` 的對象。
- `T.apply(block: T.() -> Unit)`：這是函式的簽名。它接受一個名為 `block` 的參數，這個 `block` 是一個以 `T` 類型的接收者（receiver）為上下文的函數型參數，沒有任何輸入，並且沒有返回值 (`Unit`)。
- `: T`：函式的返回類型是 `T`，也就是函式調用的對象本身。

```Java
/**
 * Calls the specified function [block] with `this` value as its receiver and returns `this` value.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#apply).
 */
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}
```

---

```JavaScript
/**
 * Calls the specified function [block] and returns its result.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#run).
 */
@kotlin.internal.InlineOnly
public inline fun <R> run(block: () -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

/**
 * Calls the specified function [block] with `this` value as its receiver and returns its result.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#run).
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

/**
 * Calls the specified function [block] with the given [receiver] as its receiver and returns its result.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#with).
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}

/**
 * Calls the specified function [block] with `this` value as its receiver and returns `this` value.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#apply).
 */
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

/**
 * Calls the specified function [block] with `this` value as its argument and returns `this` value.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#also).
 */
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.also(block: (T) -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block(this)
    return this
}

/**
 * Calls the specified function [block] with `this` value as its argument and returns its result.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#let).
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

```