الكلاسات في Java: البرمجة كائنية التوجه
Java مبنية بالكامل على فكرة البرمجة كائنية التوجه (Object-Oriented Programming): كل شيء في البرنامج يُمثَّل كـ «كائن» له بيانات وسلوك. حتى String التي تستخدمها من أول درس هي كلاس! في هذا الدرس ستتعلم كيف تصمم كلاساتك، وستفهم أخيراً معنى الكلمات التي كتبتها كثيراً: public و class و static.
الكلاس والكائن
Section titled “الكلاس والكائن”الكلاس قالب أو مخطط، والكائن (Object) نسخة حقيقية مصنوعة منه. مخطط بيت واحد يمكن أن يُبنى منه عشرات البيوت، ولكل بيت لون وعنوان مختلف.
class Student { private String name; private int age;
public Student(String name, int age) { this.name = name; this.age = age; }
public void introduce() { System.out.println("أنا " + name + " وعمري " + age + " سنة"); }}واستخدامه في main:
Student s1 = new Student("سارة", 14);Student s2 = new Student("عمر", 15);s1.introduce();s2.introduce();أنا سارة وعمري 14 سنةأنا عمر وعمري 15 سنة- الحقول (Fields):
nameوage، بيانات كل كائن. - المُنشئ (Constructor): الدالة التي اسمها نفس اسم الكلاس وبلا نوع مرجع. تُنفَّذ عند إنشاء الكائن بـ
new. this.name = name:thisتعني «هذا الكائن». الطرف الأيسر هو الحقل، والأيمن هو المعامل.- الدوال مثل
introduce: لاحظ أنها بدونstatic، لأنها تعمل على بيانات كائن محدد. لذلك نستدعيها من الكائن:s1.introduce().
التغليف: private و getters و setters
Section titled “التغليف: private و getters و setters”لاحظ أن الحقول private: لا يمكن الوصول إليها من خارج الكلاس. لو كتبت s1.age = -5 سيرفض المترجم. لماذا نمنع ذلك؟ لأننا لا نريد أعماراً سالبة أو بيانات غير منطقية!
بدلاً من ذلك، نوفر دوالاً عامة تتحكم في القراءة والتعديل:
public String getName() { return name;}
public int getAge() { return age;}
public void setAge(int age) { if (age < 0) { System.out.println("العمر لا يمكن أن يكون سالباً"); return; } this.age = age;}s2.setAge(16);s2.setAge(-3);System.out.println(s2.getAge());System.out.println(s2.getName());العمر لا يمكن أن يكون سالباً16عمرهذا هو التغليف (Encapsulation): البيانات محمية داخل الكلاس، والعالم الخارجي يتعامل معها فقط عبر دوال تتحقق من صحتها. مثل الصراف الآلي: تستطيع السحب والإيداع عبر الشاشة، لكن لا تستطيع فتح الخزنة بيدك.
toString
Section titled “toString”إذا طبعت كائناً مباشرة، ستحصل على شيء مثل Student@1b6d3586. أضف الدالة toString لتحدد شكل الطباعة:
@Overridepublic String toString() { return "Student(" + name + ", " + age + ")";}الآن System.out.println(s1) تطبع Student(سارة, 14). وكلمة @Override تخبر المترجم أنك تعيد كتابة دالة موروثة، وستفهمها في الفقرة التالية.
الوراثة: extends
Section titled “الوراثة: extends”كلاس يمكن أن يرث حقول ودوال كلاس آخر، ثم يضيف أو يعدّل:
class Animal { protected String name;
public Animal(String name) { this.name = name; }
public void speak() { System.out.println(name + " يصدر صوتاً"); }}
class Cat extends Animal { public Cat(String name) { super(name); }
@Override public void speak() { System.out.println(name + " يقول: مياو"); }}List<Animal> animals = new ArrayList<>();animals.add(new Animal("الحصان"));animals.add(new Cat("مشمش"));for (Animal a : animals) { a.speak();}الحصان يصدر صوتاًمشمش يقول: مياوextends Animal: القطة نوع من الحيوانات.super(name): استدعاء مُنشئ الكلاس الأب.protected: مثلprivate، لكن يسمح للكلاسات الوارثة بالوصول.@Override: إعادة كتابة الدالة الموروثة.
والأجمل: قائمة من نوع Animal تقبل قطة، وعند استدعاء speak تعمل نسخة القطة الخاصة. هذه الميزة اسمها تعدد الأشكال (Polymorphism).
record: كلاس البيانات المختصر
Section titled “record: كلاس البيانات المختصر”كثير من الكلاسات وظيفتها حمل البيانات فقط. منذ Java 16 يمكن كتابتها في سطر واحد:
record Book(String title, int pages) {}Book b1 = new Book("البرمجة للمبتدئين", 250);Book b2 = new Book("البرمجة للمبتدئين", 250);System.out.println(b1);System.out.println(b1.equals(b2));System.out.println(b1.title());Book[title=البرمجة للمبتدئين, pages=250]trueالبرمجة للمبتدئينالسطر الواحد يولّد تلقائياً: المُنشئ، ودوال القراءة مثل title()، و toString، و equals التي تقارن المحتوى. والحقول ثابتة لا تتغير بعد الإنشاء.
الآن تفهم main
Section titled “الآن تفهم main”public class Main { public static void main(String[] args) { ... }}public class Main: كلاس عام اسمهMain، واسم الملف يجب أن يطابقه:Main.java.static: الدالة تابعة للكلاس نفسه، فتستطيع Java تشغيلها دون إنشاء كائن منMain.void: لا تُرجع شيئاً.String[] args: مصفوفة نصوص تستقبل أي معاملات تُمرر للبرنامج من الطرفية.
جرّب بنفسك
Section titled “جرّب بنفسك”- صمم كلاس
BankAccountبحقل خاصbalance، ودالتيdepositوwithdrawترفضان المبالغ السالبة، وترفضwithdrawالسحب إذا كان الرصيد غير كافٍ. - أضف
getBalanceوtoString. - أنشئ حسابين، ونفذ عمليات مختلفة، واطبعهما.
- تحدٍّ: أنشئ
class Shapeبدالةarea()تُرجع 0، وكلاسين يرثان منه:CircleوRectangle، ثم ضع أشكالاً مختلفة في قائمة واطبع مساحاتها.
أخطاء شائعة
Section titled “أخطاء شائعة”- نسيان new:
Student s = Student("سارة", 14);خطأ. الصحيحnew Student(...). - الوصول لحقل private من الخارج: خطأ
has private access. استخدم getter أو setter. - نسيان super في مُنشئ الكلاس الوارث: إذا لم يكن للأب مُنشئ بلا معاملات، يجب استدعاء
super(...)في أول سطر.
اختبر نفسك
Section titled “اختبر نفسك”سؤال
لماذا نجعل الحقول private ونضيف getters و setters؟
هذا هو التغليف: التعديل يمر عبر دوال تتحقق من القيم، فلا تُخزن بيانات غير منطقية.
سؤال
ما وظيفة super(name) داخل مُنشئ الكلاس Cat extends Animal؟
super(...) يستدعي مُنشئ الكلاس الأب ليهيئ الحقول الموروثة مثل name.
الخلاصة
Section titled “الخلاصة”- الكلاس قالب، والكائن نسخة منه تُنشأ بـ
new، والمُنشئ يهيئ الحقول. thisتشير للكائن الحالي، والدوال غير الـ static تعمل على بيانات الكائن.- التغليف: حقول
privateمع getters و setters تتحقق من البيانات. extendsللوراثة، وsuperلمُنشئ الأب، و@Overrideلإعادة كتابة الدوال.recordلكلاسات البيانات المختصرة.