تخطَّ إلى المحتوى

الكلاسات في Java: البرمجة كائنية التوجه

الدرس 7 من 11 · Java

Java مبنية بالكامل على فكرة البرمجة كائنية التوجه (Object-Oriented Programming): كل شيء في البرنامج يُمثَّل كـ «كائن» له بيانات وسلوك. حتى String التي تستخدمها من أول درس هي كلاس! في هذا الدرس ستتعلم كيف تصمم كلاساتك، وستفهم أخيراً معنى الكلمات التي كتبتها كثيراً: public و class و static.

الكلاس قالب أو مخطط، والكائن (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): البيانات محمية داخل الكلاس، والعالم الخارجي يتعامل معها فقط عبر دوال تتحقق من صحتها. مثل الصراف الآلي: تستطيع السحب والإيداع عبر الشاشة، لكن لا تستطيع فتح الخزنة بيدك.

إذا طبعت كائناً مباشرة، ستحصل على شيء مثل Student@1b6d3586. أضف الدالة toString لتحدد شكل الطباعة:

@Override
public String toString() {
return "Student(" + name + ", " + age + ")";
}

الآن System.out.println(s1) تطبع Student(سارة, 14). وكلمة @Override تخبر المترجم أنك تعيد كتابة دالة موروثة، وستفهمها في الفقرة التالية.

كلاس يمكن أن يرث حقول ودوال كلاس آخر، ثم يضيف أو يعدّل:

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 التي تقارن المحتوى. والحقول ثابتة لا تتغير بعد الإنشاء.

public class Main {
public static void main(String[] args) { ... }
}
  • public class Main: كلاس عام اسمه Main، واسم الملف يجب أن يطابقه: Main.java.
  • static: الدالة تابعة للكلاس نفسه، فتستطيع Java تشغيلها دون إنشاء كائن من Main.
  • void: لا تُرجع شيئاً.
  • String[] args: مصفوفة نصوص تستقبل أي معاملات تُمرر للبرنامج من الطرفية.
  1. صمم كلاس BankAccount بحقل خاص balance، ودالتي deposit و withdraw ترفضان المبالغ السالبة، وترفض withdraw السحب إذا كان الرصيد غير كافٍ.
  2. أضف getBalance و toString.
  3. أنشئ حسابين، ونفذ عمليات مختلفة، واطبعهما.
  4. تحدٍّ: أنشئ class Shape بدالة area() تُرجع 0، وكلاسين يرثان منه: Circle و Rectangle، ثم ضع أشكالاً مختلفة في قائمة واطبع مساحاتها.
  • نسيان new: Student s = Student("سارة", 14); خطأ. الصحيح new Student(...).
  • الوصول لحقل private من الخارج: خطأ has private access. استخدم getter أو setter.
  • نسيان super في مُنشئ الكلاس الوارث: إذا لم يكن للأب مُنشئ بلا معاملات، يجب استدعاء super(...) في أول سطر.

سؤال

لماذا نجعل الحقول private ونضيف getters و setters؟

سؤال

ما وظيفة super(name) داخل مُنشئ الكلاس Cat extends Animal؟

  • الكلاس قالب، والكائن نسخة منه تُنشأ بـ new، والمُنشئ يهيئ الحقول.
  • this تشير للكائن الحالي، والدوال غير الـ static تعمل على بيانات الكائن.
  • التغليف: حقول private مع getters و setters تتحقق من البيانات.
  • extends للوراثة، و super لمُنشئ الأب، و @Override لإعادة كتابة الدوال.
  • record لكلاسات البيانات المختصرة.