Companion object في Kotlin: ما هو، بناء الجملة والاستخدام

المؤلف: IT Sectr نُشر: 2026-06-21 وقت القراءة: 9 دق

companion object هي آلية في لغة Kotlin تحل محل الكلمة المفتاحية static في Java لتعريف الأعضاء الثابتة للفئة. لا تحتوي Kotlin على static مدمج — بدلاً من ذلك، يتم استخدام تعريف object مع المُعدِّل companion داخل الفئة. وفقًا لـ JetBrains, 2025، يسمح companion object باستدعاء الدوال والخصائص عبر اسم الفئة، مما يجعله مكافئًا كاملاً للأعضاء الثابتة في نظام Java البيئي.

الوجبات الرئيسية

  • companion object هو تعريف object داخل فئة مع الكلمة المفتاحية companion، ليحل محل static في Java
  • الأعضاء الثابتة يمكن الوصول إليها عبر اسم الفئة دون إنشاء مثيل: ClassName.method()
  • companion object المسمى يسمح بتعيين اسم للكائن لجعل الكود أكثر قابلية للقراءة
  • الواجهات في Kotlin يمكنها أيضًا احتواء companion object مع تنفيذ الدوال
  • @JvmStatic — تعليق توضيحي لتصدير دوال companion object كدوال static حقيقية في Java

ما هو companion object في Kotlin؟

companion object هو نوع خاص من object declaration في Kotlin يتم تمييزه بالكلمة المفتاحية companion. يسمح بتعريف أعضاء ينتمون إلى الفئة وليس إلى مثيلاتها — أي أعضاء ثابتة بمصطلحات Java. على عكس Java، حيث static هو معدِّل لحقول ودوال فردية، في Kotlin يتم تجميع الأعضاء الثابتة داخل كائن مرافق واحد.

أي فئة في Kotlin يمكنها احتواء companion object واحد فقط. يمكن الوصول إلى أعضاء هذا الكائن عبر اسم الفئة دون إنشاء مثيل: MyClass.method(). من حيث البايت كود، يتم ترجمة companion object إلى فئة داخلية منفصلة، وتصبح دواله دوالًا ثابتة للفئة الخارجية عند استخدام التعليق التوضيحي @JvmStatic.

وفقًا لـ Google I/O 2024، أصبح companion object النمط الرئيسي لدوال المصنع والثوابت والدوال المساعدة في تطبيقات Android الحديثة المكتوبة بـ Kotlin. يختاره المطورون بدلاً من فئات الأدوات ذات الدوال على مستوى الحزمة لأن companion object يحافظ على الاتصال المنطقي مع الفئة المالكة.

استخدم companion object لتجميع السياق الثابت — الثوابت، المصانع، الدوال المساعدة التي تنتمي منطقيًا إلى الفئة ولكنها لا تتطلب مثيلًا.

بناء جملة companion object: التعريف والوصول

بناء جملة companion object الأساسي بسيط: توضع الكلمة المفتاحية companion قبل تعريف object داخل الفئة. إذا لم يتم تحديد اسم، يحصل الكائن على اسم Companion، والذي يمكن الوصول إليه بشكل صريح أو ضمني.

kotlin
class User {
    companion object {
        const val TABLE_NAME = "users"

        fun create(name: String): User {
            return User(name)
        }
    }

    val name: String
    constructor(name: String) { this.name = name }
}

// Access via class name
val table = User.TABLE_NAME
val user = User.create("Alice")

في المثال أعلاه، TABLE_NAME و create() يمكن الوصول إليهما عبر User.TABLE_NAME و User.create(). لاحظ المُعدِّل const للثوابت البدائية — يضمن تضمين القيمة في وقت الترجمة، مشابهًا لـ Java static final للبدائيات و String.

الوصول الضمني والصريح عبر Companion

إذا لم يكن لـ companion object اسم، يمكن الوصول إليه عبر الاسم التلقائي Companion: User.Companion.TABLE_NAME. في الممارسة العملية، نادرًا ما يكون هذا ضروريًا، لكنه مفيد عند الاستدعاء من كود Java أو عند استخدام الانعكاس.

kotlin
// Both calls are equivalent
User.create("Bob")
User.Companion.create("Bob")

// Getting reference to companion object
val companion: User.Companion = User

companion object المسمى والميزات

يمكن أن يكون لـ companion object اسم، مما يحسن قابلية قراءة الكود ويسمح بالوصول إليه باسم ذي معنى. Factory هو الاسم الأكثر شيوعًا لـ companion object المستخدم كمصنع.

kotlin
class HttpResponse {
    companion object Factory {
        fun ok(body: String): HttpResponse = HttpResponse(200, body)
        fun notFound(): HttpResponse = HttpResponse(404, "Not Found")
        fun serverError(): HttpResponse = HttpResponse(500, "Internal Error")
    }
}

// Access via Factory name
val response = HttpResponse.Factory.ok("data")

يساعد companion object المسمى في توثيق الغرض من الأعضاء الثابتة. في إعادة الهيكلة، يجعل الاسم الكود موثقًا ذاتيًا — يرى المطور فورًا أن Factory ينشئ مثيلات HttpResponse.

القيود: companion object واحد فقط لكل فئة

يمكن للفئة احتواء companion object واحد فقط. هذا فرق جوهري عن Java، حيث يمكنك تعريف أي عدد من الحقول والدوال الثابتة دون تجميع. إذا كنت بحاجة إلى عدة مجموعات منطقية من الأعضاء الثابتة، استخدم تعريفات object متداخلة بدون companion.

وفقًا لوثائق Kotlin الرسمية (Kotlin Docs, 2025)، فإن تقييد companion object واحد مدفوع بتصميم اللغة: يجب أن تكون الأعضاء الثابتة مرتبطة بإحكام بالفئة، ويوفر كائن مرافق واحد حدودًا واضحة لهذا الارتباط. إذا كان هناك عدد كبير جدًا من الأعضاء الثابتة، فهذه إشارة لإعادة هيكلة الفئة.

companion object في الواجهات

في Kotlin، يمكن للواجهات أيضًا احتواء companion object. هذا يسمح بتعريف دوال ثابتة وثوابت مباشرة داخل واجهة، وهو أمر غير ممكن في Java. هذا النهج يُستخدم غالبًا لتعريف الثوابت المرتبطة بالواجهة أو دوال المصنع.

kotlin
interface ApiService {
    companion object {
        const val BASE_URL = "https://api.example.com"
        const val TIMEOUT_MS = 5000

        fun create(client: OkHttpClient): ApiService =
            Retrofit.Builder()
                .baseUrl(BASE_URL)
                .client(client)
                .build()
                .create(ApiService::class.java)
    }
}

في هذا المثال، BASE_URL و TIMEOUT_MS و create() تنتمي إلى واجهة ApiService، وليس إلى تطبيقاتها. هذا مناسب: جميع الثوابت ومنطق المصنع لإنشاء مثيل خدمة API موجودة في مكان واحد — داخل الواجهة نفسها.

تطبيقات الواجهة لا ترث أعضاء companion object — يتم استدعاؤها فقط عبر اسم الواجهة: ApiService.BASE_URL.

@JvmStatic و @JvmField: التوافق مع Java

عند استدعاء دوال companion object من كود Java، تكون بشكل افتراضي قابلة للوصول كدوال للفئة الداخلية Companion، وليس كدوال ثابتة للفئة الخارجية. لتصديرها كدوال وحقول ثابتة حقيقية لـ Java، استخدم التعليق التوضيحي @JvmStatic للدوال و @JvmField للحقول.

التعليق التوضيحيالتطبيقالنتيجة في Java
@JvmStaticدوال companion objectدالة ثابتة: ClassName.method()
@JvmFieldحقول companion objectحقل ثابت: ClassName.field
constالبدائيات و Stringثابت مضمن: المترجم يستبدل القيمة
بدون تعليقالدوال/الحقول افتراضيًاCompanion.method() / Companion.field
kotlin
class MathUtils {
    companion object {
        const val PI = 3.14159

        @JvmStatic
        fun square(x: Int): Int = x * x

        @JvmField
        val TAG: String = "MathUtils"
    }
}

// Called in Java as MathUtils.square(5), MathUtils.TAG

يوصى باستخدام @JvmStatic لجميع دوال companion object العامة التي يجب أن تكون قابلة للوصول من كود Java. const يُطبق فقط على الأنواع البدائية و String — للأنواع الأخرى استخدم @JvmField.

أمثلة عملية لاستخدام companion object

في المشاريع الحقيقية، يُستخدم companion object لعدة سيناريوهات قياسية. دوال المصنع هي النمط الأكثر شيوعًا: بدلاً من منشئات متعددة بتوقيعات مختلفة، تُستخدم دوال مصنع مسماة في companion object.

kotlin
sealed class NetworkResult<out T> {
    data class Success<out T>(val data: T) : NetworkResult<T>()
    data class Error(val message: String) : NetworkResult<Nothing>()

    companion object {
        fun loading<T>(): NetworkResult<T> =
            Loading()
    }
}

private class Loading<T> : NetworkResult<T>()

companion object في NetworkResult يوفر دالة مصنع loading() التي تنشئ مثيل Loading بنوع generic الصحيح. بدون هذه الدالة، سيتعين عليك إنشاء مثيل مباشرة عبر Loading()، مما يكشف عن التنفيذ الداخلي.

الثوابت وعلامات التسجيل

غالبًا ما يُستخدم companion object لتخزين ثوابت خاصة بالفئة. الثوابت المُعرَّفة بـ const val يتم تضمينها في وقت الترجمة، مما يوفر عدم وجود حمل إضافي في وقت التشغيل.

kotlin
class UserRepository {
    companion object {
        private const val TAG = "UserRepository"
        private const val CACHE_SIZE = 100
        private const val DEFAULT_PAGE_SIZE = 20
    }
}

الأسئلة الشائعة

ما الفرق بين companion object و object العادي في Kotlin؟

object العادي هو singleton كامل الوجود يوجد بشكل مستقل عن الفئة. companion object هو object داخل فئة تُستدعى أعضاؤه عبر اسم الفئة الخارجية. في البايت كود، يصبح companion object فئة داخلية ثابتة، بينما يصبح object العادي فئة singleton مستقلة.

هل يمكن توريث companion object؟

لا، companion object لا يُورث بالفئات الفرعية. إذا ورثت الفئة B الفئة A، فإن B.method() لن يستدعي الدالة من companion object للفئة A — يجب الاستدعاء عبر A.method(). هذا يتوافق مع سلوك الدوال الثابتة في Java.

كيف أستدعي companion object من كود Java؟

افتراضيًا — عبر ClassName.Companion.method(). لاستدعائها كدالة ثابتة، أضف @JvmStatic إلى دالة companion object. للحقول، استخدم @JvmField أو عرّفها بـ const val للبدائيات.

لماذا لا توجد كلمة مفتاحية static في Kotlin؟

قرر مطورو Kotlin التخلي عن static لصالح companion object للعمل الموحد مع الكائنات. static في Java ينتهك مبادئ OOP لأن الدوال الثابتة غير مرتبطة بمثيل. companion object هو كائن من الدرجة الأولى يمكن تمريره وتوسيعه بدوال الامتداد وتنفيذ الواجهات.

هل يؤثر companion object على الأداء؟

عند استخدام const val للبدائيات و String — لا يوجد حمل إضافي، القيمة تُضمن في البايت كود. للدوال، لا يوجد حمل إضافي ما لم تكن الدالة inline. في معظم الحالات، لا يُحدث companion object أي تأثير قابل للقياس على الأداء. استخدم @JvmStatic فقط لواجهة API العامة المستدعاة من Java.

الخلاصة

  • companion object هي آلية في Kotlin لتعريف الأعضاء الثابتة للفئة، لتحل محل Java static
  • companion object واحد لكل فئة — يجمع كل العناصر الثابتة في كائن مرافق واحد
  • الوصول عبر اسم الفئة: ClassName.method() أو ClassName.Companion.method()
  • @JvmStatic و @JvmField يصدران الأعضاء كدوال وحقول ثابتة حقيقية لـ Java
  • const val للبدائيات و String — يُضمن القيمة في وقت الترجمة بدون حمل إضافي
  • الواجهات تدعم أيضًا companion object للثوابت ودوال المصنع
  • companion object المسمى يحسن قابلية قراءة الكود ويوثق ذاتيًا غرض الأعضاء الثابتة

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا