companion object هي آلية في لغة Kotlin تحل محل الكلمة المفتاحية static في Java لتعريف الأعضاء الثابتة للفئة. لا تحتوي Kotlin على static مدمج — بدلاً من ذلك، يتم استخدام تعريف object مع المُعدِّل companion داخل الفئة. وفقًا لـ JetBrains, 2025، يسمح companion object باستدعاء الدوال والخصائص عبر اسم الفئة، مما يجعله مكافئًا كاملاً للأعضاء الثابتة في نظام Java البيئي.
الوجبات الرئيسية
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، والذي يمكن الوصول إليه بشكل صريح أو ضمني.
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 object اسم، يمكن الوصول إليه عبر الاسم التلقائي Companion: User.Companion.TABLE_NAME. في الممارسة العملية، نادرًا ما يكون هذا ضروريًا، لكنه مفيد عند الاستدعاء من كود Java أو عند استخدام الانعكاس.
// Both calls are equivalent
User.create("Bob")
User.Companion.create("Bob")
// Getting reference to companion object
val companion: User.Companion = User
يمكن أن يكون لـ companion object اسم، مما يحسن قابلية قراءة الكود ويسمح بالوصول إليه باسم ذي معنى. Factory هو الاسم الأكثر شيوعًا لـ companion object المستخدم كمصنع.
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 واحد فقط. هذا فرق جوهري عن Java، حيث يمكنك تعريف أي عدد من الحقول والدوال الثابتة دون تجميع. إذا كنت بحاجة إلى عدة مجموعات منطقية من الأعضاء الثابتة، استخدم تعريفات object متداخلة بدون companion.
وفقًا لوثائق Kotlin الرسمية (Kotlin Docs, 2025)، فإن تقييد companion object واحد مدفوع بتصميم اللغة: يجب أن تكون الأعضاء الثابتة مرتبطة بإحكام بالفئة، ويوفر كائن مرافق واحد حدودًا واضحة لهذا الارتباط. إذا كان هناك عدد كبير جدًا من الأعضاء الثابتة، فهذه إشارة لإعادة هيكلة الفئة.
في Kotlin، يمكن للواجهات أيضًا احتواء companion object. هذا يسمح بتعريف دوال ثابتة وثوابت مباشرة داخل واجهة، وهو أمر غير ممكن في Java. هذا النهج يُستخدم غالبًا لتعريف الثوابت المرتبطة بالواجهة أو دوال المصنع.
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.
عند استدعاء دوال companion object من كود Java، تكون بشكل افتراضي قابلة للوصول كدوال للفئة الداخلية Companion، وليس كدوال ثابتة للفئة الخارجية. لتصديرها كدوال وحقول ثابتة حقيقية لـ Java، استخدم التعليق التوضيحي @JvmStatic للدوال و @JvmField للحقول.
| التعليق التوضيحي | التطبيق | النتيجة في Java |
|---|---|---|
| @JvmStatic | دوال companion object | دالة ثابتة: ClassName.method() |
| @JvmField | حقول companion object | حقل ثابت: ClassName.field |
| const | البدائيات و String | ثابت مضمن: المترجم يستبدل القيمة |
| بدون تعليق | الدوال/الحقول افتراضيًا | Companion.method() / Companion.field |
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.
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 يتم تضمينها في وقت الترجمة، مما يوفر عدم وجود حمل إضافي في وقت التشغيل.
class UserRepository {
companion object {
private const val TAG = "UserRepository"
private const val CACHE_SIZE = 100
private const val DEFAULT_PAGE_SIZE = 20
}
}
الأسئلة الشائعة
object العادي هو singleton كامل الوجود يوجد بشكل مستقل عن الفئة. companion object هو object داخل فئة تُستدعى أعضاؤه عبر اسم الفئة الخارجية. في البايت كود، يصبح companion object فئة داخلية ثابتة، بينما يصبح object العادي فئة singleton مستقلة.
لا، companion object لا يُورث بالفئات الفرعية. إذا ورثت الفئة B الفئة A، فإن B.method() لن يستدعي الدالة من companion object للفئة A — يجب الاستدعاء عبر A.method(). هذا يتوافق مع سلوك الدوال الثابتة في Java.
افتراضيًا — عبر ClassName.Companion.method(). لاستدعائها كدالة ثابتة، أضف @JvmStatic إلى دالة companion object. للحقول، استخدم @JvmField أو عرّفها بـ const val للبدائيات.
قرر مطورو Kotlin التخلي عن static لصالح companion object للعمل الموحد مع الكائنات. static في Java ينتهك مبادئ OOP لأن الدوال الثابتة غير مرتبطة بمثيل. companion object هو كائن من الدرجة الأولى يمكن تمريره وتوسيعه بدوال الامتداد وتنفيذ الواجهات.
عند استخدام const val للبدائيات و String — لا يوجد حمل إضافي، القيمة تُضمن في البايت كود. للدوال، لا يوجد حمل إضافي ما لم تكن الدالة inline. في معظم الحالات، لا يُحدث companion object أي تأثير قابل للقياس على الأداء. استخدم @JvmStatic فقط لواجهة API العامة المستدعاة من Java.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا