Reified ใน Kotlin — คืออะไร ไวยากรณ์ และการนำไปใช้

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-06-21 เวลาอ่าน: 8 นาที

reified — คำสำคัญในภาษา Kotlin ที่อนุญาตให้เข้าถึงประเภทของ generic-พารามิเตอร์ภายใน inline-ฟังก์ชันในขณะรันไทม์ ใน generics ทั่วไปจะใช้ type erasure — ข้อมูลเกี่ยวกับประเภทจะถูกลบในขั้นตอนการคอมไพล์ แต่ reified จะเก็บรักษาไว้ ตาม Kotlin Documentation, 2025 reified ทำงานเฉพาะภายใน inline-ฟังก์ชันเท่านั้น เนื่องจากคอมไพเลอร์จะแทรกประเภทจริง ในขั้นตอนการอินไลน์

สาระสำคัญ

  • reified — ตัวปรับแต่งพารามิเตอร์ generic ที่เก็บข้อมูลเกี่ยวกับประเภทในรันไทม์
  • Inline only — reified ทำงานเฉพาะภายใน inline-ฟังก์ชันเท่านั้น
  • Type erasure — กลไกมาตรฐานของ Java/Kotlin ที่ลบประเภท generic; reified ข้ามผ่านมัน
  • การตรวจสอบ is — เป็นไปได้: if (value is T) แทน if (value is String)
  • การสร้างอินสแตนซ์ — การสร้าง T::class.java.newInstance() โดยไม่ต้องส่ง Class

Reified ใน Kotlin คืออะไร?

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 ใน generics

Type erasure — กลไกของ Java และ Kotlin ที่ข้อมูลเกี่ยวกับพารามิเตอร์ generic ถูกลบระหว่างการคอมไพล์ ในไบต์โค้ด List และ List กลายเป็นเพียง List สิ่งนี้ ทำเพื่อความเข้ากันได้ย้อนหลังกับ Java 1.4 ซึ่งไม่มี generics แต่สร้างข้อจำกัดเมื่อทำงานกับประเภทในรันไทม์

kotlin
// ❌ ข้อผิดพลาด: ไม่สามารถตรวจสอบอินสแตนซ์ของประเภทที่ถูกลบได้
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 อย่างชัดเจน แต่ต้องใช้ boilerplate: ทุกการเรียกมาพร้อมกับ .java หรือ ::class.java reified จะกำจัด boilerplate นี้อย่างสมบูรณ์

ไวยากรณ์ของ reified และกลไกการทำงาน

ตัวปรับแต่ง reified วางไว้หน้าพารามิเตอร์ generic ใน inline-ฟังก์ชัน ฟังก์ชันจะต้องเป็น inline — คอมไพเลอร์ต้องสามารถแทรกประเภทเฉพาะในขั้นตอนการอินไลน์ได้

kotlin
inline fun <reified T> isA(value: Any): Boolean {
    return value is T
}

fun main() {
    println(isA<String>("สวัสดี")) // true
    println(isA<Int>("สวัสดี"))  // false
}

ระหว่างการคอมไพล์ การเรียก isA("Hello") จะถูกแทนที่ด้วยการตรวจสอบ value is String การเรียก isA("Hello") จะถูกแทนที่ด้วย value is Int ประเภทจะถูกแทรกตามตัวอักษร ทำให้สามารถใช้ is, as, ::class และการดำเนินการอื่น ๆ ที่ไม่สามารถใช้ได้กับ type erasure

การดีคอมไพล์ฟังก์ชัน reified

หากดีคอมไพล์ไบต์โค้ดของ isA("Hello") IntelliJ IDEA จะแสดงผลลัพธ์ประมาณนี้ใน Java: String.class.isInstance(value) แทนที่พารามิเตอร์ generic คอมไพเลอร์ได้แทรก java.lang.String.class เฉพาะ — ไม่มีการรีเฟลกชันในการค้นหาประเภทตามชื่อ มีเพียงการอ้างอิงคลาสโดยตรง

การตรวจสอบประเภทด้วย reified: is และ as

การใช้งาน reified ที่พบบ่อยที่สุด — การตรวจสอบประเภทผ่านตัวดำเนินการ is ใน generic-ฟังก์ชันทั่วไป value is T จะไม่คอมไพล์ แต่ด้วย reified มันทำงานเหมือนกับคลาสทั่วไป: value is String, value is List (เกือบ — โดยคำนึงถึงข้อจำกัดของ reified สำหรับประเภทที่มีพารามิเตอร์)

kotlin
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(list) ด้วยพารามิเตอร์ Class ด้วย reified การเรียกจะอ่านได้เหมือนการดำเนินการตามธรรมชาติกับรายการ ซึ่งช่วยเพิ่มความสามารถในการอ่านของสายโซ่การประมวลผลข้อมูล

ตาม Kotlin Coroutines Guide (JetBrains, 2025) การตรวจสอบประเภทแบบ reified ถูกใช้ใน launch และ async สำหรับการส่งประเภทผลลัพธ์ของ coroutine ซึ่งช่วยหลีกเลี่ยงการระบุประเภทอย่างชัดเจนในกรณีส่วนใหญ่

รีเฟลกชันด้วย reified: การสร้างอินสแตนซ์และการเข้าถึง Class

reified ให้การเข้าถึง T::class — การอ้างอิงถึง KClass ซึ่งสามารถรับ Java Class ได้ผ่าน .java สิ่งนี้เปิดโอกาสสำหรับการสร้างอินสแตนซ์ผ่านรีเฟลกชัน การทำงานกับซีเรียลไลเซอร์ และการรับคำอธิบายประกอบของคลาสในระหว่างรันไทม์

kotlin
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(json) ได้ สิ่งนี้อาจดูเหมือนการปรับปรุงเล็กน้อย แต่ในโปรเจกต์ที่มีการเรียกซีเรียลไลซ์หลายร้อยครั้ง reified จะลด boilerplate ลงอย่างมากและทำให้โค้ดสะอาดขึ้น

ข้อจำกัดของ reified และทางเลือกอื่น

reified มีข้อจำกัด ประการแรก — ทำงานเฉพาะภายใน inline-ฟังก์ชันเท่านั้น หากฟังก์ชันไม่สามารถทำให้เป็น inline ได้ (เช่น เป็นแบบเรียกซ้ำหรือใหญ่เกินไป) reified จะไม่สามารถใช้ได้ ประการที่สอง — ไม่สามารถใช้ reified กับ suspend-ฟังก์ชันโดยตรงได้ ต้องผ่าน inline-ตัวห่อเท่านั้น

ประการที่สาม — reified ไม่ทำงานกับประเภทที่มีพารามิเตอร์อย่างสมบูรณ์ ตัวอย่างเช่น filterByType>() อาจให้ผลลัพธ์ที่ไม่คาดคิด เนื่องจากสำหรับประเภทที่มีพารามิเตอร์ reified จะเก็บเฉพาะประเภทดิบ (List) โดยไม่ระบุอาร์กิวเมนต์ generic สำหรับการตรวจสอบประเภทที่มีพารามิเตอร์อย่างสมบูรณ์ จำเป็นต้องใช้รีเฟลกชันกับ TypeToken

การดำเนินการด้วย reifiedไม่มี reified
value is T✅ ใช้ได้❌ ข้อผิดพลาดในการคอมไพล์
T::class✅ ใช้ได้❌ ข้อผิดพลาดในการคอมไพล์
List is T⚠️ เฉพาะ raw type❌ ข้อผิดพลาด
การสร้างอินสแตนซ์✅ ผ่านรีเฟลกชัน❌ ต้องใช้ Class
Suspend-ฟังก์ชัน❌ ผ่านตัวห่อ inline เท่านั้น❌ ใช้ไม่ได้

สำหรับกรณีที่ไม่สามารถใช้ reified ได้ ให้ใช้รูปแบบการส่ง Class หรือ TypeToken จากไลบรารีอย่างชัดเจน (เช่น Gson TypeToken หรือ Jackson TypeReference) วิธีนี้ใช้ได้กับทุกฟังก์ชัน แต่ต้องใช้ boilerplate และสะดวกน้อยกว่า

คำถามที่พบบ่อย

ทำไม reified ทำงานเฉพาะกับ inline-ฟังก์ชัน?

คอมไพเลอร์แทนที่พารามิเตอร์ reified T ด้วยประเภทเฉพาะระหว่างการอินไลน์เนื้อหาของฟังก์ชัน หากฟังก์ชันไม่ใช่ inline คอมไพเลอร์ไม่มีที่ที่จะแทรกประเภท — การเรียก generic-ฟังก์ชันเกิดขึ้นผ่านไบต์โค้ดเดียวที่ T ถูกลบ Inline สร้างสำเนาไบต์โค้ดแยกต่างหากสำหรับแต่ละประเภท-อาร์กิวเมนต์

สามารถประกาศ reified property ได้หรือไม่?

ไม่ reified ใช้ได้เฉพาะกับพารามิเตอร์ของฟังก์ชันเท่านั้น สำหรับ properties ใช้รูปแบบ inline fun ที่ส่งคืนค่า หรือส่ง Class อย่างชัดเจนผ่านคอนสตรักเตอร์ Extension properties ก็ไม่รองรับ reified เช่นกัน

reified ทำงานกับ nullable-ประเภทอย่างไร?

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 มี overhead หรือไม่?

น้อยมาก reified ไม่ใช้รีเฟลกชัน — คอมไพเลอร์แทรกประเภทเฉพาะในขั้นตอนการอินไลน์ ในไบต์โค้ดนี่คือการอ้างอิงคลาสโดยตรง (ldc + checkcast/invokevirtual) ไม่มี overhead เมื่อเทียบกับการส่ง Class ด้วยตนเอง — ทั้งสองวิธีสร้างไบต์โค้ดที่เหมือนกัน

สามารถใช้ reified ในการพัฒนา Android ได้หรือไม่?

ได้ reified ถูกใช้อย่างแพร่หลายใน Android Bundle.getParcelable(), Intent.getSerializableExtra(), viewModels() จาก Android KTX — ฟังก์ชันทั้งหมดนี้ใช้ reified เพื่อหลีกเลี่ยงการส่ง Class อย่างชัดเจน ตาม Google Android Docs (2025) แนะนำให้ใช้ reified สำหรับ generic-API ที่ต้องการประเภทในรันไทม์

สรุป

  • reified — ตัวปรับแต่งพารามิเตอร์ generic ของ inline-ฟังก์ชันที่เก็บประเภทในรันไทม์
  • Type erasure — กลไกมาตรฐานในการลบประเภท; reified ข้ามผ่านมันโดยการอินไลน์
  • is/as — การตรวจสอบและการแปลงประเภททำงานกับ reified เหมือนกับคลาสทั่วไป
  • การอ้างอิงคลาส — T::class และ T::class.java พร้อมใช้งานสำหรับรีเฟลกชันและซีเรียลไลซ์
  • Inline เท่านั้น — reified เป็นไปไม่ได้หากไม่มี inline-ฟังก์ชันเนื่องจากกลไกการแทรกประเภท
  • ประเภทที่มีพารามิเตอร์ — reified ไม่เก็บอาร์กิวเมนต์ generic (เฉพาะ raw type)
  • การนำไปใช้ — การซีเรียลไลซ์ Gson/Moshi, คอนเทนเนอร์ DI, การตรวจสอบประเภทในคอลเล็กชัน, Android KTX

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม