كيف تتوقف عن المعاناة وتجد بديلاً لـ Java static في Kotlin
\n\nهل سبق لك أن أعدت كتابة كود Java القديم الجيد إلى Kotlin واصطدمت فجأة بجدار؟ يبدو الأمر بسيطًا ومنطقيًا وموجزًا... ولكنك تريد إنشاء طريقة ثابتة — ويقول Kotlin: «لا يا صديقي، ليس لدي ذلك». هل هذا ذعر؟ لا داعي. هذا السؤال على Stack Overflow حصد آلاف المشاهدات، وليس من دون سبب: الموضوع مهم حقًا لكل من ينتقل من Java إلى Kotlin.
\n\nفي هذه المقالة، لن نقدم إجابة جافة فقط. سنشرح مكافئ طرق Java static في Kotlin من منظور الأداء والتحسين وسهولة الصيانة. ستتعلم كيف لا تفقد السرعة ولا تقع في الأخطاء. هيا بنا!
\n\nلماذا لا يوجد static في Kotlin وما الحاجة إليه؟
\n\nلنبدأ مباشرة. في Java، الطرق الثابتة (static methods) هي طرق تنتمي إلى الفئة وليس إلى نسخة منها. تعيش في الذاكرة مرة واحدة، ولا تتطلب إنشاء كائن، وهي مثالية للدوال المساعدة أو المصانع أو الكائنات المفردة.
\n\n// Java\npublic class MathUtils {\n public static int square(int x) {\n return x * x;\n }\n}\n\n// الاستخدام\nint result = MathUtils.square(5);\n\n\nفي Kotlin، لا توجد كلمة مفتاحية static. لماذا؟ لأن Kotlin يشجع النهج الكائني والوظيفي في آن واحد. بدلاً من static، تُستخدم هنا:
\n\n- \n
- companion object (كائن الرفيق) \n
- الدوال على المستوى الأعلى (top-level functions) (دوال خارج الفئة) \n
- object (كائن مفرد) \n
لكل خيار خصائصه من حيث الأداء والتحسين. دعنا نناقشها بالترتيب.
\n\nالخيار 1: companion object — هل هو المكافئ المباشر لـ static؟
\n\nالإجابة الأكثر شيوعًا التي ستجدها على Stack Overflow هي companion object. يبدو كالتالي:
\n\n// Kotlin\nclass MathUtils {\n companion object {\n fun square(x: Int): Int = x * x\n }\n}\n\n// الاستخدام\nval result = MathUtils.square(5)\n\n\nبصريًا، هو مطابق تمامًا لـ Java. لكن تحت الغطاء، الأمر مختلف. companion object هو كائن حقيقي، وريث لـ Any. يتم إنشاؤه مرة واحدة عند تحميل الفئة ويُخزن في الذاكرة ككائن عادي. هذا يعني:
- \n
- الذاكرة: أكبر قليلاً من Java static (بسبب النفقات الإضافية للكائن). \n
- الاستدعاء: يقوم مترجم Kotlin بإنشاء طريقة ثابتة، ولكن فقط إذا أضفت التعليق التوضيحي
@JvmStatic. \n
@JvmStatic: سحر التوافق البيني
\n\nإذا كنت تكتب بلغة Kotlin ولكن كودك يُستدعى من Java، فبدون @JvmStatic ستواجه مفاجأة غير سارة:
// Kotlin\nclass MyClass {\n companion object {\n @JvmStatic\n fun greet() = "Hello!"\n }\n}\n\n// استدعاء من Java\nMyClass.greet(); // يعمل بفضل @JvmStatic\n// بدون @JvmStatic كان عليك كتابة MyClass.Companion.greet()\n\n\nمن منظور الأداء، يتم تجميع @JvmStatic إلى طريقة ثابتة حقيقية على JVM. هذا يعني أنك تحصل على نفس السرعة تمامًا كما في Java. بدون هذا التعليق التوضيحي، يتم الاستدعاء عبر كائن Companion، مما يسبب زيادة طفيفة في النفقات (أقل من 1 نانوثانية، ولكن للأنظمة عالية التحميل قد يكون هذا مهمًا).
الخيار 2: الدوال على المستوى الأعلى (Top-level functions) — طريقة Kotlin
\n\nيسمح Kotlin بتعريف الدوال مباشرة في الملف، خارج الفئة. يُسمى هذا الدوال على المستوى الأعلى (top-level function). يحولها المترجم إلى طرق ثابتة لفئة غلاف (اسم الملف + Kt).
\n\n// الملف: MathUtils.kt\nfun square(x: Int): Int = x * x\n\n// الاستخدام\nval result = square(5)\n\n\nتحت الغطاء، هذا هو:
\n\n// Java (مفكك)\npublic final class MathUtilsKt {\n public static final int squa