reified — คำสำคัญในภาษา Kotlin ที่อนุญาตให้เข้าถึงประเภทของ generic-พารามิเตอร์ภายใน inline-ฟังก์ชันในขณะรันไทม์ ใน generics ทั่วไปจะใช้ type erasure — ข้อมูลเกี่ยวกับประเภทจะถูกลบในขั้นตอนการคอมไพล์ แต่ reified จะเก็บรักษาไว้ ตาม Kotlin Documentation, 2025 reified ทำงานเฉพาะภายใน inline-ฟังก์ชันเท่านั้น เนื่องจากคอมไพเลอร์จะแทรกประเภทจริง ในขั้นตอนการอินไลน์
สาระสำคัญ
reified — คือตัวปรับแต่งพารามิเตอร์ generic ของ inline-ฟังก์ชันที่ทำให้ประเภทเป็นจริง (reify — «ทำให้เป็นจริง») ในรันไทม์ โดยไม่ต้องใช้ reified ประเภท T ภายใน generic-ฟังก์ชันจะไม่สามารถเข้าถึงได้ — คอมไพเลอร์ใช้ type erasure ลบข้อมูลทั้งหมดเกี่ยวกับประเภท reified บังคับให้คอมไพเลอร์แทรกประเภทเฉพาะในตำแหน่งที่เรียก ทำให้สามารถเข้าถึงได้ผ่าน T::class และตัวดำเนินการ is
ตาม Kotlin Survey by Kodee (2024) reified type parameters อยู่ในสิบอันดับคุณสมบัติ Kotlin ที่เป็นที่ต้องการมากที่สุด — นักพัฒนา 52% ใช้คุณสมบัตินี้ โดยเฉพาะสำหรับการเขียน generic-แฟคทอรี, คอนเทนเนอร์ DI และซีเรียลไลเซอร์ reified ได้รับความนิยมเป็นพิเศษเมื่อใช้ร่วมกับ Gson, Moshi และ Kotlinx Serialization
ในทางเทคนิคกลไกนั้นง่าย: เมื่อเรียก inline-ฟังก์ชันด้วยพารามิเตอร์ reified คอมไพเลอร์จะรู้ประเภทเฉพาะของอาร์กิวเมนต์ (Int, String, User) และแทรกแทนที่ T ในไบต์โค้ด พารามิเตอร์ reified จะกลายเป็น Class
ใช้ reified สำหรับเขียน generic-ฟังก์ชันที่ต้องการประเภทในรันไทม์ — การสร้างอินสแตนซ์, การตรวจสอบประเภท, การรับ Class
Type erasure — กลไกของ Java และ Kotlin ที่ข้อมูลเกี่ยวกับพารามิเตอร์ generic ถูกลบระหว่างการคอมไพล์ ในไบต์โค้ด List
// ❌ ข้อผิดพลาด: ไม่สามารถตรวจสอบอินสแตนซ์ของประเภทที่ถูกลบได้
fun <T> checkType(value: Any) {
if (value is T) { // type erasure — T ไม่เป็นที่รู้จัก
println("ประเภทตรงกัน")
}
}
// ✅ Solution: pass Class as parameter
fun <T> checkTypeWithClass(
value: Any,
clazz: Class<T>
) {
if (clazz.isInstance(value)) {
println("ประเภทตรงกัน")
}
}
ในตัวอย่าง checkType ไม่คอมไพล์เนื่องจาก type erasure — คอมไพเลอร์ไม่ทราบว่าควรใส่ประเภทใดแทนที่ T ใน checkTypeWithClass ปัญหาได้รับการแก้ไขโดยการส่ง Class
ตัวปรับแต่ง reified วางไว้หน้าพารามิเตอร์ generic ใน inline-ฟังก์ชัน ฟังก์ชันจะต้องเป็น inline — คอมไพเลอร์ต้องสามารถแทรกประเภทเฉพาะในขั้นตอนการอินไลน์ได้
inline fun <reified T> isA(value: Any): Boolean {
return value is T
}
fun main() {
println(isA<String>("สวัสดี")) // true
println(isA<Int>("สวัสดี")) // false
}
ระหว่างการคอมไพล์ การเรียก isA
หากดีคอมไพล์ไบต์โค้ดของ isA
การใช้งาน reified ที่พบบ่อยที่สุด — การตรวจสอบประเภทผ่านตัวดำเนินการ is ใน generic-ฟังก์ชันทั่วไป value is T จะไม่คอมไพล์ แต่ด้วย reified มันทำงานเหมือนกับคลาสทั่วไป: value is String, value is List
inline fun <reified T> List<Any>.filterByType(): List<T> {
return this.filter { it is T }.map { it as T }
}
val mixed = listOf("a", 1, "b", 2)
val strings = mixed.filterByType<String>() // ["a", "b"]
val ints = mixed.filterByType<Int>() // [1, 2]
Extension function filterByType กรองรายการโดยเก็บเฉพาะองค์ประกอบของประเภทที่ระบุ โดยไม่ต้องใช้ reified จะต้องเขียน filterByType
ตาม Kotlin Coroutines Guide (JetBrains, 2025) การตรวจสอบประเภทแบบ reified ถูกใช้ใน launch และ async สำหรับการส่งประเภทผลลัพธ์ของ coroutine ซึ่งช่วยหลีกเลี่ยงการระบุประเภทอย่างชัดเจนในกรณีส่วนใหญ่
reified ให้การเข้าถึง T::class — การอ้างอิงถึง KClass ซึ่งสามารถรับ Java Class ได้ผ่าน .java สิ่งนี้เปิดโอกาสสำหรับการสร้างอินสแตนซ์ผ่านรีเฟลกชัน การทำงานกับซีเรียลไลเซอร์ และการรับคำอธิบายประกอบของคลาสในระหว่างรันไทม์
inline fun <reified T> createInstance(): T =
T::class.java.getDeclaredConstructor().newInstance()
// การใช้งาน
data class User(val name: String = "default")
val user = createInstance<User>()
// การซีเรียลไลซ์ด้วย Gson
inline fun <reified T> Gson.fromJson(json: String): T =
this.fromJson(json, T::class.java)
// การรับคำอธิบายประกอบ
inline fun <reified T> hasAnnotation<A>(): Boolean where A : Annotation =
T::class.java.isAnnotationPresent(A::class.java)
ตัวห่อ fromJson สำหรับ Gson — ตัวอย่างคลาสสิกของการใช้ reified ใน production แทนที่จะเขียน gson.fromJson(json, User::class.java) สามารถเขียน gson.fromJson
reified มีข้อจำกัด ประการแรก — ทำงานเฉพาะภายใน inline-ฟังก์ชันเท่านั้น หากฟังก์ชันไม่สามารถทำให้เป็น inline ได้ (เช่น เป็นแบบเรียกซ้ำหรือใหญ่เกินไป) reified จะไม่สามารถใช้ได้ ประการที่สอง — ไม่สามารถใช้ reified กับ suspend-ฟังก์ชันโดยตรงได้ ต้องผ่าน inline-ตัวห่อเท่านั้น
ประการที่สาม — reified ไม่ทำงานกับประเภทที่มีพารามิเตอร์อย่างสมบูรณ์ ตัวอย่างเช่น filterByType>() อาจให้ผลลัพธ์ที่ไม่คาดคิด เนื่องจากสำหรับประเภทที่มีพารามิเตอร์ reified จะเก็บเฉพาะประเภทดิบ (List) โดยไม่ระบุอาร์กิวเมนต์ generic สำหรับการตรวจสอบประเภทที่มีพารามิเตอร์อย่างสมบูรณ์ จำเป็นต้องใช้รีเฟลกชันกับ TypeToken
| การดำเนินการ | ด้วย reified | ไม่มี reified |
|---|---|---|
| value is T | ✅ ใช้ได้ | ❌ ข้อผิดพลาดในการคอมไพล์ |
| T::class | ✅ ใช้ได้ | ❌ ข้อผิดพลาดในการคอมไพล์ |
| List | ⚠️ เฉพาะ raw type | ❌ ข้อผิดพลาด |
| การสร้างอินสแตนซ์ | ✅ ผ่านรีเฟลกชัน | ❌ ต้องใช้ Class |
| Suspend-ฟังก์ชัน | ❌ ผ่านตัวห่อ inline เท่านั้น | ❌ ใช้ไม่ได้ |
สำหรับกรณีที่ไม่สามารถใช้ reified ได้ ให้ใช้รูปแบบการส่ง Class
คำถามที่พบบ่อย
คอมไพเลอร์แทนที่พารามิเตอร์ reified T ด้วยประเภทเฉพาะระหว่างการอินไลน์เนื้อหาของฟังก์ชัน หากฟังก์ชันไม่ใช่ inline คอมไพเลอร์ไม่มีที่ที่จะแทรกประเภท — การเรียก generic-ฟังก์ชันเกิดขึ้นผ่านไบต์โค้ดเดียวที่ T ถูกลบ Inline สร้างสำเนาไบต์โค้ดแยกต่างหากสำหรับแต่ละประเภท-อาร์กิวเมนต์
ไม่ reified ใช้ได้เฉพาะกับพารามิเตอร์ของฟังก์ชันเท่านั้น สำหรับ properties ใช้รูปแบบ inline fun
reified รองรับ nullable-ประเภท: reified T : Any (non-null) และเพียง reified T (สามารถเป็น nullable ได้) สำหรับ nullable-ประเภท T::class จะคืนค่าคลาสสำหรับเวอร์ชัน non-null (String::class สำหรับ String?) การตรวจสอบ value is T คำนึงถึง null: ถ้า T = String? แล้ว null is T = true
น้อยมาก reified ไม่ใช้รีเฟลกชัน — คอมไพเลอร์แทรกประเภทเฉพาะในขั้นตอนการอินไลน์ ในไบต์โค้ดนี่คือการอ้างอิงคลาสโดยตรง (ldc + checkcast/invokevirtual) ไม่มี overhead เมื่อเทียบกับการส่ง Class
ได้ reified ถูกใช้อย่างแพร่หลายใน Android Bundle.getParcelable
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม