الانتماء إلى الصنف لا إلى الكائن
معظم الحقول والطرق التي كتبتها حتى الآن تنتمي إلى الكائنات: في كل مرة تستدعي فيها new، يحصل كل كائن على نسخته الخاصة من الحقول، وتعمل الطرق على حالة ذلك الكائن المحدد. وتقلب الكلمة المفتاحية static هذا الأمر. فالعضو static ينتمي إلى الصنف نفسه - توجد منه نسخة واحدة يتشاركها كل الكائنات، وهو موجود سواء أنشأت كائنًا يومًا أم لم تنشئ.
هذا التمييز الوحيد يفسّر حقول static، وطرق static، والثوابت، بل وحتى لماذا تكون main دائمًا static.
حقل static مشترك
يمنح الحقل غير الساكن كل كائن مكانه الخاص. أما الحقل static فيمنح الصنف مكانًا واحدًا يتشاركه الجميع. المثال الكلاسيكي هو عدّاد يتتبع كم كائنًا أُنشئ:
لاحظ أنك تقرأ العدّاد بصيغة User.count - عبر اسم الصنف، لأن القيمة لا تنتمي إلى أي User بعينه. أما كل name فيعيش داخل كائنه الخاص. غيّر count عبر أي كائن، وسترى كل الكائنات القيمة الجديدة، لأنه لا يوجد منه سوى واحد.
الطرق static
تنتمي الطريقة static إلى الصنف أيضًا، لذا تستدعيها على الصنف دون كائن:
هذا هو النمط نفسه الكامن وراء المكتبة القياسية: Math.max وInteger.parseInt وArrays.sort وList.of كلها static - سلوك مساعد لا يحتاج إلى كائن ليعمل عليه. الجأ إلى طريقة static عندما يعتمد العمل على وسائطه فقط، لا على أي حالة خاصة بكائن.
المطبّ الكبير: static لا يمكنها رؤية instance
تعمل الطريقة static دون أي كائن، فلا يوجد this. وهذا يعني أنها لا تستطيع الوصول إلى حقول الكائن أو استدعاء طرق الكائن مباشرةً - فلا يوجد كائن محدد تقرؤها منه. وهذا أكثر خطأ شائع لدى المبتدئين مع static:
class Account {
int balance = 100; // حقل خاص بالكائن
static int show() {
return balance; // خطأ في الترجمة: الحقل غير الساكن 'balance'
} // لا يمكن الإشارة إليه من سياق ساكن
}
الحل إما جعل الطريقة طريقة كائن (بإزالة static، فيصبح لها this)، أو تمرير الكائن صراحةً:
أما الاتجاه المعاكس فلا مشكلة فيه: تستطيع طريقة الكائن أن تقرأ حقول static بحرية وأن تستدعي طرق static، لأن البيانات المشتركة على مستوى الصنف موجودة دائمًا.
الثوابت باستخدام static final
اجمع بين static وfinal لتحصل على ثابت: قيمة واحدة مشتركة لا يمكن أن تتغير أبدًا. وبحسب العُرف تُسمّى هذه الثوابت بنمط UPPER_SNAKE_CASE:
static final هي الطريقة الاصطلاحية للتعبير عن "قيمة ثابتة تنتمي إلى النوع" - أشياء مثل Integer.MAX_VALUE أو Math.PI في المكتبة القياسية. جعلها static يعني أنك لا تهدر نسخة لكل كائن؛ وجعلها final يعني أنه لا يمكن لأحد إعادة إسنادها.
كتل التهيئة static
يمكن تهيئة الحقل static البسيط في سطره مباشرةً. وعندما يحتاج الإعداد إلى منطق فعلي - بناء جدول بحث، أو قراءة إعدادات - استخدم كتلة static. فهي تُنفَّذ مرة واحدة، عند تحميل الصنف لأول مرة، قبل وجود أي كائن:
تنطلق الكتلة مرة واحدة فقط مهما استخدمت الصنف، مما يجعلها المكان المناسب للإعداد لمرة واحدة على مستوى الصنف بأكمله.
متى تستخدم static
قاعدة عملية سريعة:
- استخدم حقل static فقط للبيانات المشتركة فعلًا بين كل الكائنات - عدّاد، أو ذاكرة تخزين مؤقت (cache)، أو ثابت. فإذا كان من المعقول أن يحمل كائنان قيمتين مختلفتين، فينبغي أن يكون حقل كائن بدلاً من ذلك.
- استخدم طريقة static عندما تعتمد النتيجة على الوسائط فقط، لا على حالة أي كائن (الدوال المساعدة النقية).
- اجعل الأصل هو أعضاء الكائن (instance). فالإفراط في استخدام
staticيحوّل برنامجك بهدوء إلى كومة من الحالة العامة، يصعب اختبارها والتفكير فيها.staticهي الاستثناء، لا نقطة الانطلاق.
التالي: التعدادات (Enums)
الثابت static final مناسب لقيمة ثابتة واحدة، لكن عندما يكون لديك مجموعة صغيرة وثابتة من القيم المترابطة - الاتجاهات، أيام الأسبوع، حالات الطلب - فلدى Java نوع مخصص لهذا الغرض، أكثر أمانًا وتعبيرًا من الثوابت المبعثرة. هذا هو enum، وهو موضوع الصفحة التالية.
الأسئلة الشائعة
ماذا تعني static في Java؟
تعني static أن الحقل أو الطريقة ينتمي إلى الصنف نفسه، لا إلى كائن فردي. توجد نسخة واحدة بالضبط من الحقل static يتشاركها كل الكائنات، وتُستدعى الطريقة static على الصنف (Math.max(...)) دون الحاجة إلى كائن. أما الأعضاء غير الساكنة (أعضاء الكائن) فيحصل كل كائن منها على نسخة جديدة خاصة به.
ما الفرق بين المتغيرات static ومتغيرات الكائن في Java؟
متغير الكائن له قيمة واحدة لكل كائن - يمكن لكائنين أن يحملا قيمتين مختلفتين. أما المتغير static فله قيمة واحدة يتشاركها كل كائنات الصنف، لذا فإن تغييره عبر كائن واحد (أو عبر اسم الصنف) يكون مرئيًا لكل الكائنات الأخرى. استخدم حقول الكائن للحالة الخاصة بكل كائن، وحقول static للبيانات العامة فعلًا على مستوى الصنف، مثل عدّاد أو ثابت.
لماذا تكون طريقة main ساكنة (static) في Java؟
تحتاج آلة Java الافتراضية (JVM) إلى استدعاء main قبل أن يوجد أي كائن من صنفك. وبما أن الطريقة static تنتمي إلى الصنف لا إلى كائن، يمكن لبيئة التشغيل استدعاء Main.main(args) مباشرةً دون إنشاء Main أولًا. لهذا السبب يكون التوقيع دائمًا public static void main(String[] args).