引言
先看一段代碼:
/**
* 高階函數:安全解析JSON消息
* @param jsonMessage JSON消息字符串
* @param onSuccess 解析成功回調
* @param onError 解析失敗回調(可選)
*/
private inline fun <reified T> safeParseJson(
jsonMessage: String,
onSuccess: (T) -> Unit,
noinline onError: ((Exception) -> Unit)? = null
) {
try {
val result = gson.fromJson(jsonMessage, T::class.java)
onSuccess(result)
} catch (e: JsonSyntaxException) {
Log.e(TAG, "JSON解析失敗 - 類型: ${T::class.simpleName}, error: ${e.message}")
Log.e(TAG, "問題JSON: ${jsonMessage.take(300)}")
onError?.invoke(e)
} catch (e: Exception) {
Log.e(TAG, "解析異常 - 類型: ${T::class.simpleName}, error: ${e.message}")
onError?.invoke(e)
}
}
<reified T>
這reified 是什麼意思??
kotlin reified
一、先理解問題:泛型為什麼“有時候拿不到類型”?
你知道 Kotlin 和 Java 裏都有泛型,比如:
fun <T> printType(item: T) {
println(item::class.java)
}
你可能以為能輸出類型,但如果你這樣調用:
printType(listOf("abc"))
其實有些情況下,程序拿不到 T 的真實類型。
因為——Kotlin(跟 Java 一樣)在運行時會“擦除泛型”(叫做 Type Erasure)。
也就是説:
你在寫代碼時有類型信息,
但程序運行的時候,這個類型就被“抹掉”了。
二、那“被抹掉”會造成什麼問題?
比如你想寫一個通用的 JSON 解析函數:
fun <T> parse(json: String): T {
return Gson().fromJson(json, T::class.java)
}
這行其實會報錯,因為:
Kotlin 編譯器説:“T 是什麼我不知道,你給我個 Class 才能解析。”
三、於是 Kotlin 提供了一個魔法:reified
當你在 內聯函數(inline function) 里加上 reified,
Kotlin 就會在編譯時幫你把類型“保留下來”。
比如:
inline fun <reified T> parse(json: String): T {
return Gson().fromJson(json, T::class.java)
}
這樣就能用了!
四、為什麼要 inline + reified 一起用?
因為:
inline:讓函數在編譯時“展開”,也就是直接把函數體複製到調用的地方;reified:只有在 inline 的函數裏才能用,因為只有這樣,編譯器才能知道 T 的真實類型是什麼。
所以
reified必須依附在inline上。
五、用例對比:有 vs 沒有 reified
❌ 沒有 reified:
fun <T> parse(json: String): T {
return Gson().fromJson(json, T::class.java) // ❌ 報錯,拿不到 T 的類型
}
✅ 有 reified:
inline fun <reified T> parse(json: String): T {
return Gson().fromJson(json, T::class.java) // ✅ 可以用
}
六、現實中的用法(比如你那段 safeParseJson)
private inline fun <reified T> safeParseJson(
jsonMessage: String,
onSuccess: (T) -> Unit,
noinline onError: ((Exception) -> Unit)? = null
)
這裏 reified T 就讓你能這樣寫:
safeParseJson<User>(json) { user ->
println("用户名:${user.name}")
}
不用你手動傳 User::class.java,Kotlin 會自動知道 T 是 User。
七、總結一句話
|
特性
|
作用
|
舉例
|
|
|
函數在編譯時展開,提高性能
|
|
|
|
讓泛型在運行時還能“知道類型”
|
|
|
兩者一起
|
能在運行時用 |
JSON解析、反射、類型判斷等場景
|
要是再用一句生活比喻來記:
平時的泛型函數就像“快遞包裝盒”,標籤被撕掉了,你不知道里面是什麼;
加上reified後,就像“盒子外寫清楚了名字”,打開之前你就知道里面是啥。
一張圖表示:
=================================================================
Java 的泛型
一、Java 的泛型是“類型擦除”的
在 Java 中,所有泛型信息在編譯後都會被“擦除”,只保留原始類型。
例如👇:
List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
這兩個在運行時其實是一樣的類型,都只是 ArrayList。
👉 JVM 不知道泛型參數 String 或 Integer,因為:
Java 的泛型是“編譯期檢查 + 運行期擦除”。
舉個例子:
public <T> void printType(T item) {
System.out.println(item.getClass());
}
調用:
printType("Hello"); // 輸出 class java.lang.String
printType(123); // 輸出 class java.lang.Integer
看似沒問題,但其實函數本身在運行時並不知道“我這裏是 <String> 還是 <Int>”,
它只是根據傳入的值推斷類型。
如果你想在 Java 中手動保留泛型信息,就得像這樣:
public <T> T fromJson(String json, Class<T> clazz) {
return gson.fromJson(json, clazz);
}
必須額外傳 Class<T>,因為 Java 自己無法保留類型。
Kotlin 的 reified 可以在運行時拿到類型
Kotlin 的 reified 是一種編譯期技巧 + 語法糖。
inline fun <reified T> parse(json: String): T {
return gson.fromJson(json, T::class.java)
}
Kotlin 編譯器會在編譯時直接把 T 的真實類型寫進去(比如 User::class.java)。
所以:
Kotlin 在 inline + reified 的情況下,可以直接拿到類型,不需要像 Java 一樣手動傳。
對比表
|
特性
|
Java 泛型
|
Kotlin 普通泛型
|
Kotlin |
|
泛型信息
|
編譯期存在,運行期被擦除
|
一樣被擦除
|
✅ 編譯時保留類型
|
|
是否能用 |
❌ 不行
|
❌ 不行
|
✅ 可以
|
|
是否能直接做 JSON 解析
|
❌ 需手動傳 |
❌ 同樣需要
|
✅ 不用傳,自動識別
|
|
編譯方式
|
普通編譯
|
普通編譯
|
|
用一句話總結:
Java 的泛型信息在運行時是“丟失”的;
Kotlin 的reified泛型能在運行時“記得”自己是誰。
舉例對比
Java 寫法:
User user = parse(json, User.class);
Kotlin 沒有 reified 的寫法:
val user = parse(json, User::class.java)
Kotlin 有 reified 的寫法:
val user = parse<User>(json)
✅ 簡潔、直觀、少傳參數。