reified هي كلمة مفتاحية في لغة Kotlin تسمح بالوصول إلى نوع المعلم العام داخل الدوال المضمنة في وقت التنفيذ. في الأنواع العامة العادية، يتم تطبيق محو النوع (type erasure) — تتم مسح معلومات النوع أثناء الترجمة، ولكن reified تحافظ عليها. وفقًا لـ توثيق Kotlin، 2025، يعمل reified فقط داخل الدوال المضمنة لأن المترجم يستبدل النوع الحقيقي في مرحلة التضمين.
النقاط الرئيسية
reified هي محدث لمعلم عام لدالة مضمنة يجعل النوع حقيقيًا (reify — «تجسيد») في وقت التنفيذ. بدون reified، فإن النوع T داخل دالة عامة غير قابل للوصول — يطبق المترجم محو النوع، مما يزيل جميع معلومات النوع. reified تجبر المترجم على استبدال النوع المحدد في موقع الاستدعاء، مما يجعله قابلًا للوصول عبر T::class وعامل is.
وفقًا لـ استطلاع Kotlin بواسطة Kodee (2024)، تحتل معلمات النوع reified مكانًا بين أكثر عشر ميزات Kotlin طلبًا — يستخدمها 52% من المطورين الذين شملتهم الاستطلاع، وذلك أساسًا لكتابة المصانع العامة وحاويات DI والمسلسلات. تحظى reified بشعبية خاصة بالاقتران مع Gson و Moshi و Kotlinx Serialization.
تقنيًا، الآلية بسيطة: عند استدعاء دالة مضمنة بمعلم reified، يعرف المترجم نوع الوسيطة المحدد (Int، String، User) ويستبدله بـ T. في البايت كود، يتحول المعلم reified إلى Class<T> عادي يتم تمريره كوسيط مخفي.
استخدم reified لكتابة دوال عامة حيث يكون النوع مطلوبًا في وقت التنفيذ — إنشاء الكائنات، فحوص الأنواع، الحصول على Class<T> للانعكاس أو التسلسل.
محو النوع هو آلية في Java و Kotlin حيث تتم مسح معلومات المعلم العام أثناء الترجمة. في البايت كود، تصبح List<String> و List<Int> مجرد List. هذا تم للتوافق مع الإصدار Java 1.4 الذي لم يكن يحتوي على أنواع عامة، ولكنه يخلق قيودًا عند العمل مع الأنواع في وقت التنفيذ.
// ❌ خطأ: لا يمكن التحقق من مثيل النوع الممحو
fun <T> checkType(value: Any) {
if (value is T) { // محو النوع — T غير معروف
println("النوع متطابق")
}
}
// ✅ Solution: pass Class as parameter
fun <T> checkTypeWithClass(
value: Any,
clazz: Class<T>
) {
if (clazz.isInstance(value)) {
println("النوع متطابق")
}
}
في المثال، checkType لا يتم ترجمته بسبب محو النوع — المترجم لا يعرف أي نوع يستبدل بـ T. في checkTypeWithClass، يتم حل المشكلة بتمرير Class<T> صراحة، ولكن هذا يتطلب كتابة مملة (boilerplate): كل استدعاء يصحبه .java أو ::class.java. reified تلغي هذه الكتابة المملة بالكامل.
يوضع المحدث reified قبل المعلم العام في دالة مضمنة. يجب أن تكون الدالة مضمنة — يجب أن يكون المترجم قادرًا على استبدال النوع المحدد في مرحلة التضمين.
inline fun <reified T> isA(value: Any): Boolean {
return value is T
}
fun main() {
println(isA<String>("مرحبًا")) // true
println(isA<Int>("مرحبًا")) // false
}
أثناء الترجمة، يتم استبدال استدعاء isA<String>(«Hello») بالفحص value is String. ويتم تحويل استدعاء isA<Int>(«Hello») إلى value is Int. يتم استبدال النوع حرفيًا، مما يتيح استخدام is، as، ::class وعمليات أخرى غير متاحة مع محو النوع.
إذا قمت بفك ترجمة البايت كود لـ isA<String>(«Hello»)، سيظهر IntelliJ IDEA نتيجة مماثلة بالتقريب في Java: String.class.isInstance(value). بدلاً من معلم عام، استبدل المترجم java.lang.String.class المحدد — لا انعكاس مع البحث عن النوع بالاسم، فقط مرجع مباشر إلى الفئة.
الاستخدام الأكثر شيوعًا لـ reified هو فحص الأنواع عبر عامل is. في دالة عامة عادية، لا يتم ترجمة value is T. مع reified، يعمل كما لو كان فئة عادية: value is String، value is List<Int> (تقريبًا — مع قيود للأنواع المعلملة).
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]
تقوم دالة التمديد filterByType بتصفية قائمة، محتفظة فقط بالعناصر من النوع المحدد. بدون reified، كان عليك كتابة filterByType<String>(list) بمعلم Class<String>. مع reified، يقرأ الاستدعاء كعملية طبيعية على قائمة، مما يحسن قراءة سلاسل معالجة البيانات.
وفقًا لـ دليل Kotlin Coroutines (جيتبراينز، 2025)، تستخدم فحوص النوع reified في launch و async لتمرير نوع نتيجة الكوروتين، مما يتجنب تحديد النوع الصريح في معظم الحالات.
توفر reified الوصول إلى T::class — مرجع إلى KClass، ومنه يمكن الحصول على 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 في الإنتاج. بدلاً من gson.fromJson(json, User::class.java)، يمكنك كتابة gson.fromJson<User>(json). قد يبدو هذا تحسينًا طفيفًا، ولكن في مشروع يحتوي على مئات استدعاءات التسلسل، تقلل reified الكتابة المملة بشكل كبير وتجعل الكود أنظف.
reified لها قيود. أولاً — تعمل فقط داخل الدوال المضمنة. إذا لم يكن بالإمكان جعل الدالة مضمنة (على سبيل المثال، إذا كانت مكررة أو كبيرة جدًا)، فإن reified غير متاحة. ثانيًا — لا يمكن استخدام reified مع دوال suspend مباشرة، فقط من خلال أغلفة مضمنة.
ثالثًا — reified لا تعمل بشكل كامل مع الأنواع المعلملة. على سبيل المثال، filterByType<List<String>>() قد يعطي نتائج غير متوقعة لأنه بالنسبة للأنواع المعلملة، reified تحافظ فقط على النوع الخام (raw type) — List، دون الوسائط العامة. للفحص الكامل للأنواع المعلملة، يلزم الانعكاس مع TypeToken.
| العملية | مع reified | بدون reified |
|---|---|---|
| value is T | ✅ تعمل | ❌ خطأ في الترجمة |
| T::class | ✅ تعمل | ❌ خطأ في الترجمة |
| List<String> is T | ⚠️ نوع خام فقط | ❌ خطأ |
| إنشاء كائن | ✅ عبر الانعكاس | ❌ يحتاج Class<T> |
| دالة suspend | ❌ فقط عبر غلاف مضمن | ❌ غير قابل للتطبيق |
للحالات التي لا تتوفر فيها reified، استخدم نمط التمرير الصريح لـ Class<T> أو TypeToken من المكتبات (على سبيل المثال، Gson TypeToken أو Jackson TypeReference). يعمل هذا النهج في أي دالة ولكنه يتطلب كتابة مملة وهو أقل راحة.
الأسئلة الشائعة
يستبدل المترجم المعلم reified T بالنوع المحدد أثناء تضمين جسم الدالة. إذا لم تكن الدالة مضمنة، فليس لدى المترجم مكان لاستبدال النوع — يتم استدعاء الدالة العامة من خلال بايت كود واحد حيث يتم مسح T. الدالة المضمنة تنشئ نسخة بايت كود منفصلة لكل نوع وسيط.
لا، reified تنطبق فقط على معلمات الدوال. للخصائص، استخدم نمط inline fun <reified T> مع قيمة إرجاع، أو قم بتمرير Class<T> صراحة عبر الباني. خصائص التمديد لا تدعم reified أيضًا.
reified تدعم الأنواع القابلة للصفر: reified T : Any (غير صفري) ومجرد reified T (يمكن أن يكون صفريًا). بالنسبة للأنواع القابلة للصفر، يعيد T::class الفئة للنسخة غير الصفرية (String::class لـ String?). يراعي فحص value is T القيمة null: إذا كان T = String?، فإن null is T = true.
أدنى الحد. reified لا تستخدم الانعكاس — يستبدل المترجم النوع المحدد في مرحلة التضمين. في البايت كود، هذا مرجع مباشر إلى الفئة (ldc + checkcast/invokevirtual). لا يوجد حمول إضافي مقارنة بتمرير Class<T> يدويًا — كلا النهجين يولدان نفس البايت كود.
نعم، تستخدم reified بنشاط في Android. Bundle.getParcelable<T>()، Intent.getSerializableExtra<T>()، viewModels<T>() من Android KTX — جميع هذه الدوال تستخدم reified لتجنب تمرير Class<T> صراحة. وفقًا لـ وثائق Android من Google (2025)، توصى reified لواجهات API العامة حيث يكون النوع مطلوبًا في وقت التنفيذ.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا