أساسيات البرمجة الكائنيّة

الحقول والدوال الساكنة (Static)

15 دقيقة الدرس 6 من 14

الحقول والدوال الساكنة (Static)

كل حقل ودالة كتبتهما حتى الآن ينتميان إلى كائن بعينه — لكل كائن BankAccount رصيده الخاص، واستدعاء deposit() على كائن لا يمسّ الآخرين. لكن في بعض الأحيان تحتاج إلى بيانات أو سلوك ينتمي إلى الصنف نفسه، مشتركًا بين كل الكائنات. هذا بالضبط ما تمنحك إياه الكلمة المفتاحية static.

الأعضاء المرتبطة بالكائن مقابل الأعضاء المرتبطة بالصنف

الفرق بسيط لكنه مهم:

  • عضو مرتبط بكائن (Instance) — لكل كائن نسخته الخاصة. يتم الوصول إليه عبر مرجع الكائن (account.balance).
  • عضو ساكن (Static / Class) — نسخة واحدة مشتركة بين كل الصنف. يُصَل إليه عبر اسم الصنف (BankAccount.totalAccounts).

تخيّل الأمر هكذا: قد يتتبّع صنف Car عدد السيارات التي صُنعت. هذا العدد لا ينتمي لـسيارة واحدة — بل ينتمي لمفهوم السيارة بالكامل.

الحقول الساكنة (Static Fields)

تُعلن الحقل الساكن بإضافة الكلمة static قبل النوع:

public class BankAccount { // حقل ساكن — نسخة واحدة مشتركة بين كل كائنات BankAccount private static int totalAccounts = 0; // حقل مرتبط بالكائن — لكل كائن نسخته الخاصة private double balance; public BankAccount(double initialBalance) { this.balance = initialBalance; totalAccounts++; // زيادة العداد المشترك عند كل حساب جديد } public static int getTotalAccounts() { return totalAccounts; } }
public class Main { public static void main(String[] args) { System.out.println(BankAccount.getTotalAccounts()); // 0 BankAccount a1 = new BankAccount(500.0); BankAccount a2 = new BankAccount(1200.0); System.out.println(BankAccount.getTotalAccounts()); // 2 } }

بغضّ النظر عن عدد كائنات BankAccount الموجودة، فإن totalAccounts هو عدد صحيح واحد في الذاكرة. إنشاء كائن جديد يزيده للجميع.

الدوال الساكنة (Static Methods)

تُستدعى الدالة الساكنة على الصنف لا على كائن. ولأنه لا يوجد كائن ضمنيًا، لا تستطيع الدالة الساكنة الوصول إلى حقول الكائن أو استدعاء دواله — فلا يوجد مرجع this.

public class MathHelper { // أداة مساعدة خالصة — لا حالة، لا حاجة لكائن public static double circleArea(double radius) { return Math.PI * radius * radius; } public static int clamp(int value, int min, int max) { if (value < min) return min; if (value > max) return max; return value; } }
public class Main { public static void main(String[] args) { double area = MathHelper.circleArea(5.0); System.out.println(area); // 78.53981633974483 int clamped = MathHelper.clamp(150, 0, 100); System.out.println(clamped); // 100 } }

لقد استخدمت الدوال الساكنة طوال الوقت في Java: Math.sqrt() وInteger.parseInt() ونقطة الدخول public static void main(String[] args) كلها دوال ساكنة.

لماذا تكون main ساكنة؟ تحتاج الآلة الافتراضية (JVM) إلى استدعاء main دون إنشاء كائن أوّلًا. تتيح الخاصية الساكنة للبيئة التشغيلية استدعاء الدالة مباشرةً على الصنف — دون الحاجة إلى مُنشئ.

الثوابت عبر static final

الثابت هو قيمة لا تتغيّر أبدًا. في Java، تُعلن الثوابت بـ static final وتُسمّى بالأحرف الكبيرة مع الشرطة السفلية UPPER_SNAKE_CASE وفق التقليد المتعارف عليه:

public class PhysicsConstants { public static final double SPEED_OF_LIGHT = 299_792_458.0; // متر في الثانية public static final double GRAVITATIONAL_CONSTANT = 6.674e-11; }
public class Circle { public static final double PI = 3.14159265358979; private double radius; public Circle(double radius) { this.radius = radius; } public double area() { return PI * radius * radius; // استخدام ثابت الصنف } }
  • static — نسخة واحدة للصنف، لا إهدار للذاكرة لكل كائن.
  • final — لا يمكن إعادة تعيين القيمة بعد التهيئة.
فضّل الثوابت على الأرقام السحرية (Magic Numbers). كتابة Circle.PI تُخبر القارئ بما تعنيه القيمة. بينما كتابة 3.14159 متناثرةً في عشرات الدوال خطر صيانة — فإذا احتجت يومًا تغيير الدقة، فعليك إيجاد كل موضع وتعديله.

كتل التهيئة الساكنة (Static Initializer Blocks)

أحيانًا يحتاج الحقل الساكن إلى أكثر من تعيين بسيط للتهيئة — مثل بناء جدول بحث. استخدم كتلة التهيئة الساكنة:

import java.util.HashMap; import java.util.Map; public class HttpStatus { public static final Map<Integer, String> CODES; static { CODES = new HashMap<>(); CODES.put(200, "OK"); CODES.put(404, "Not Found"); CODES.put(500, "Internal Server Error"); } }
public class Main { public static void main(String[] args) { System.out.println(HttpStatus.CODES.get(404)); // Not Found } }

تُنفَّذ الكتلة مرة واحدة عندما تُحمَّل الصنف لأول مرة من قِبل JVM — قبل إنشاء أي كائن.

الأخطاء الشائعة

لا تستطيع الدوال الساكنة استخدام this أو حقول الكائن. إذا استدعيت دالة كائن من سياق ساكن، يُشير المُصرِّف فورًا إلى الخطأ: "non-static method cannot be referenced from a static context". هذا من أكثر أخطاء المبتدئين شيوعًا في Java.
الوصول إلى عضو ساكن عبر مرجع كائن يُصرِّف لكنه مُضلِّل. كتابة a1.getTotalAccounts() تبدو كأنها تستدعي شيئًا على a1، لكنها تقرأ قيمة الصنف المشتركة. استخدم دائمًا اسم الصنف (BankAccount.getTotalAccounts()) لتوضيح القصد.

متى تستخدم static؟

  • دوال مساعدة / أدوات — حسابات خالصة بلا حالة (مثل Math.abs()).
  • الثوابت — قيم متماثلة لكل مستخدمي الصنف.
  • عدادات أو ذاكرات تخزين مؤقت على مستوى الصنف — بيانات تنتمي للنوع لا لكائن.
  • دوال المصنع (Factory Methods) — دوال ساكنة تبني كائنات وتُعيدها (ستأتي لاحقًا).

تجنّب جعل كل شيء ساكنًا لمجرد تجنّب إنشاء كائنات. الإفراط في استخدام static يُضعف البرمجة الكائنية — يكسر التغليف، يُصعّب الاختبار، ويُدخل حالة مشتركة خفية يمكن أن تُسبب أخطاءً دقيقة.

الخلاصة

الحقول والدوال الساكنة تنتمي إلى الصنف لا إلى كائنات بعينها. استخدم static للعدادات المشتركة، والدوال المساعدة، والثوابت (static final). تُهيّئ كتل التهيئة الساكنة حالةً ساكنة مركّبة عند تحميل الصنف. ابقِ استخدام static محدودًا — الجأ إليه حين يعود المفهوم فعلًا للنوع، لا حين تريد فقط تخطّي إنشاء كائن.