Skip to content

3. الفئات والواجهات

3.1. الكائن من خلال المثال

3.1.1. مقدمة

ننتقل الآن، من خلال الأمثلة، إلى البرمجة الكائنية. الكائن هو كيان يحتوي على بيانات تحدد حالته (تسمى سمات أو خصائص) ووظائف (تسمى طرق). يتم إنشاء الكائن وفقًا لنموذج يسمى فئة:

public class C1{
    type1 p1;            // الخاصية p1
    type2 p2;            // الخاصية p2
    
    type3 m3(){        // طريقة m3
        
    }
    type4 m4(){        // طريقة m4
        
    }
    
}

من الفئة C1 السابقة، يمكن إنشاء العديد من الكائنات O1، O2،... وستكون لجميعها الخصائص p1، p2،... والطرق m3، m4،... وستكون لها قيم مختلفة لخصائصها pi، وبذلك يكون لكل منها حالة خاصة بها.

إذا كان O1 كائنًا من النوع C1، فإن O1.p1 يشير إلى الخاصية p1 الخاصة بـ O1، ويشير O1.m1 إلى الطريقة m1 الخاصة بـ O1.

لنأخذ نموذجًا أوليًا للكائن: الفئة personne.

3.1.2. تعريف فئة «person»

سيكون تعريف الفئة personne كما يلي:


import java.io.*;

public class personne{
  // السمات
  private String prenom;
  private String nom;
  private int age;
  
  // طريقة
  public void initialise(String P, String N, int age){
    this.prenom=P;
    this.nom=N;
    this.age=age;
  }

  // طريقة
  public void identifie(){
    System.out.println(prenom+","+nom+","+age);
  }
}

لدينا هنا تعريف لفئة، أي نوع من أنواع البيانات. عند إنشاء متغيرات من هذا النوع، سنسميها كائنات. الفئة هي إذن قالب يتم بناء الكائنات بناءً عليه.

يمكن أن تكون عناصر أو حقول الفئة بيانات أو طرق (وظائف). ويمكن أن تحتوي هذه الحقول على أحد السمات الثلاثة التالية:

privé: لا يمكن الوصول إلى الحقل الخاص (private) إلا من خلال الطرق الداخلية للفئة

public: يمكن الوصول إلى الحقل العام (public) من خلال أي دالة محددة أو غير محددة داخل الفئة

protégé: الحقل المحمي (protected) لا يمكن الوصول إليه إلا من خلال الطرق الداخلية للفئة أو كائن مشتق (انظر لاحقًا مفهوم التوريث).

بشكل عام، يتم إعلان بيانات الفئة على أنها خاصة بينما يتم إعلان طرقها على أنها عامة. وهذا يعني أن مستخدم الكائن (المبرمج)

أ: لن يتمكن من الوصول مباشرة إلى البيانات الخاصة للكائن

ب: سيتمكن من استخدام الطرق العامة للكائن، ولا سيما تلك التي تتيح الوصول إلى بياناته الخاصة.

صيغة إعلان الكائن هي كما يلي:


public class nomClasse{
    private  donnée ou méthode privée
    public  donnée ou méthode publique
    protected  donnée ou méthode protégée
}

Remarques

  • ترتيب إعلان السمات private و protected و public غير مهم.

3.1.3. تقوم الطريقة بتهيئة

لنعد إلى فئة personne المُعلنة على النحو التالي:


import java.io.*;

public class personne{
  // السمات
  private String prenom;
  private String nom;
  private int age;
  
  // طريقة
  public void initialise(String P, String N, int age){
    this.prenom=P;
    this.nom=N;
    this.age=age;
  }

  // طريقة
  public void identifie(){
    System.out.println(prenom+","+nom+","+age);
  }
}

ما هو دور الطريقة initialise؟ نظرًا لأن الاسم واللقب والعمر هي بيانات خاصة بفئة الشخص، فإن التعليمات

personne p1;
p1.prenom="Jean";
p1.nom="Dupont";
p1.age=30;

غير قانونية. علينا تهيئة كائن من النوع personne عبر طريقة عامة. وهذا هو دور الطريقة initialise. سنكتب:

personne p1;
p1.initialise("Jean","Dupont",30);

الكتابة p1.initialise صحيحة لأن initialise متاحة للجمهور.

3.1.4. المشغل new

تسلسل التعليمات

personne p1;
p1.initialise("Jean","Dupont",30);

غير صحيحة. التعليمات

    personne p1;

تعلن p1 كمرجع إلى كائن من النوع personne. هذا الكائن غير موجود بعد، وبالتالي لم يتم تهيئة p1. وكأننا نكتب:

personne p1=null;

حيث نشير صراحةً باستخدام الكلمة الرئيسية null إلى أن المتغير p1 لا يشير بعد إلى أي كائن.

وعندما نكتب بعد ذلك

p1.initialise("Jean","Dupont",30);

فإننا نستدعي الطريقة initialise للكائن المشار إليه بواسطة p1. لكن هذا الكائن لا يوجد بعد، وسيشير المُجمِّع إلى الخطأ. لكي تشير p1 إلى كائن، يجب كتابة:

personne p1=new personne();

يؤدي ذلك إلى إنشاء كائن من النوع personne لم يتم تهيئته بعد: ستكون قيمة السمتين nom و prenom، اللتين هما إشارات إلى كائنات من النوع String، هي null، و age القيمة 0. لذلك هناك تهيئة افتراضية. والآن بما أن p1 يشير إلى كائن، فإن تعليمة تهيئة هذا الكائن

p1.initialise("Jean","Dupont",30);

صحيحة.

3.1.5. الكلمة الرئيسية this

لنلقِ نظرة على كود الطريقة initialise:


public void initialise(String P, String N, int age){
    this.prenom=P;
    this.nom=N;
    this.age=age;
  }

تعني التعليمات this.prenom=P أن السمة prenom للكائن الحالي (this) تتلقى القيمة P. تشير الكلمة الرئيسية this إلى الكائن الحالي: الكائن الذي توجد فيه الطريقة المنفذة. كيف نعرف ذلك؟ لننظر إلى كيفية تهيئة الكائن المشار إليه بواسطة p1 في البرنامج المستدعي:

p1.initialise("Jean","Dupont",30);

يتم استدعاء الطريقة initialise للكائن p1. عندما يتم الإشارة إلى الكائن this في هذه الطريقة، فإننا نشير في الواقع إلى الكائن p1. كان من الممكن أيضًا كتابة الطريقة initialise على النحو التالي:


public void initialise(String P, String N, int age){
   prenom=P;
   nom=N;
   this.age=age;
  }

عندما تشير إحدى طرق الكائن إلى سمة A لهذا الكائن، فإن الكتابة this.A تكون ضمنية. يجب استخدامها بشكل صريح عند وجود تعارض في المعرفات. وهذا هو الحال في التعليمات التالية:


this.age=age;

حيث يشير age إلى سمة للكائن الحالي وكذلك إلى المعلمة age التي تتلقاها الطريقة. يجب عندئذٍ إزالة الغموض عن طريق تسمية السمة age بـ this.age.

3.1.6. برنامج اختبار

فيما يلي برنامج اختبار:


public class test1{
  public static void main(String arg[]){
    personne p1=new personne();
    p1.initialise("Jean","Dupont",30);
    p1.identifie();
  }
}

يتم تعريف الفئة personne في ملف المصدر personne.java ويتم تجميعها:

E:\data\serge\JAVA\BASES\OBJETS\2>javac personne.java

E:\data\serge\JAVA\BASES\OBJETS\2>dir
10/06/2002  09:21                  473 personne.java
10/06/2002  09:22                  835 personne.class
10/06/2002  09:23                  165 test1.java

ونفعل الشيء نفسه بالنسبة لبرنامج الاختبار:

E:\data\serge\JAVA\BASES\OBJETS\2>javac test1.java

E:\data\serge\JAVA\BASES\OBJETS\2>dir
10/06/2002  09:21                  473 personne.java
10/06/2002  09:22                  835 personne.class
10/06/2002  09:23                  165 test1.java
10/06/2002  09:25                  418 test1.class

قد يكون من المستغرب أن البرنامج test1.java لا يستورد الفئة personne باستخدام الأمر:

import personne;

عندما يصادف المُجمِّع في شفرة المصدر إشارة إلى فئة غير مُعرَّفة في نفس ملف المصدر هذا، فإنه يبحث عن الفئة في أماكن مختلفة:

  • في الحزم المستوردة بواسطة الأوامر import
  • في الدليل الذي تم تشغيل المُجمِّع منه

في مثالنا، تم تشغيل المُجمِّع من الدليل الذي يحتوي على الملف personne.class، وهو ما يفسر أنه عثر على تعريف الفئة personne. وفي هذه الحالة، يؤدي وضع تعليمة import إلى حدوث خطأ في التجميع:

E:\data\serge\JAVA\BASES\OBJETS\2>javac test1.java
test1.java:1: '.' expected
import personne;
               ^
1 error

لتجنب هذا الخطأ ولكن للتذكير بضرورة استيراد فئة Person، سنكتب في المستقبل في بداية البرنامج:

// الفئات المستوردة
// استيراد شخص؛

يمكننا الآن تشغيل الملف test1.class:

E:\data\serge\JAVA\BASES\OBJETS\2>java test1
Jean,Dupont,30

من الممكن تجميع عدة فئات في ملف مصدر واحد. لذا، دعونا نجمع الفئتين personne و test1 في ملف المصدر test2.java. يتم تغيير اسم الفئة test1 إلى test2 لمراعاة تغيير اسم ملف المصدر:

// الحزم المستوردة
import java.io.*;

class personne{
   // السمات
  private String prenom;    // الاسم الأول لشخصيتي
  private String nom;            // اسمه
  private int age;                // عمره

   // الطريقة
  public void initialise(String P, String N, int age){
    this.prenom=P;
    this.nom=N;
    this.age=age;
  }//التهيئة

   // الطريقة
  public void identifie(){
    System.out.println(prenom+","+nom+","+age);
  }//يحدد
}//فئة
public class test2{
  public static void main(String arg[]){
    personne p1=new personne();
    p1.initialise("Jean","Dupont",30);
    p1.identifie();
  }
}

تجدر الإشارة إلى أن الفئة personne لم تعد تحتوي على السمة public. في الواقع، في ملف مصدر جافا، يمكن لفئة واحدة فقط أن تحتوي على السمة public. وهي الفئة التي تحتوي على الدالة main. علاوة على ذلك، يجب أن يحمل ملف المصدر اسم هذه الدالة. لنقوم بتجميع ملف test2.java:

E:\data\serge\JAVA\BASES\OBJETS\3>dir
10/06/2002  09:36                  633 test2.java

E:\data\serge\JAVA\BASES\OBJETS\3>javac test2.java

E:\data\serge\JAVA\BASES\OBJETS\3>dir
10/06/2002  09:36                  633 test2.java
10/06/2002  09:41                  832 personne.class
10/06/2002  09:41                  418 test2.class

يُلاحظ أنه تم إنشاء ملف .class لكل فئة موجودة في ملف المصدر. لنقم الآن بتشغيل الملف test2.class:

E:\data\serge\JAVA\BASES\OBJETS\2>java test2
Jean,Dupont,30

بعد ذلك، سنستخدم الطريقتين بالتبادل:

  • تجميع الفئات في ملف مصدر واحد
  • فئة واحدة لكل ملف مصدر

3.1.7. طريقة أخرى للتهيئة

لنأخذ الفئة personne مرة أخرى ونضيف إليها الطريقة التالية:


public void initialise(personne P){
    prenom=P.prenom;
    nom=P.nom;
    this.age=P.age;
  }

لدينا الآن طريقتان تحملان الاسم initialise: وهذا أمر مقبول طالما أنهما تقبلان معلمات مختلفة. وهذا هو الحال هنا. أصبحت المعلمة الآن مرجعًا P لشخص ما. ثم يتم تعيين سمات الشخص P إلى الكائن الحالي (this). يُلاحظ أن الطريقة initialise لديها وصول مباشر إلى سمات الكائن P على الرغم من أن هذه السمات من النوع private. هذا صحيح دائمًا: تتمتع طرق كائن O1 من فئة C دائمًا بإمكانية الوصول إلى السمات الخاصة للكائنات الأخرى من نفس الفئة C.

فيما يلي اختبار للفئة الجديدة personne:


// استيراد شخص؛
import java.io.*;

public class test1{
  public static void main(String arg[]){
    personne p1=new personne();
    p1.initialise("Jean","Dupont",30);
    System.out.print("p1=");
    p1.identifie();
    personne p2=new personne();
    p2.initialise(p1);
    System.out.print("p2=");
    p2.identifie();
  }
}

ونتيجتها:

p1=Jean,Dupont,30
p2=Jean,Dupont,30

3.1.8. منشئات فئة Person

المنشئ هو طريقة تحمل اسم الفئة ويتم استدعاؤها عند إنشاء الكائن. وعادة ما يتم استخدامها لتهيئته. وهي طريقة يمكن أن تقبل معلمات ولكنها لا تعطي أي نتيجة. ولا يسبق نموذجها الأولي أو تعريفها أي نوع (ولا حتى void).

إذا كانت الفئة تحتوي على منشئ يقبل n معلمات argi، فيمكن إعلان وتهيئة كائن من هذه الفئة بالشكل التالي:


        classe objet =new classe(arg1,arg2, ... argn);

أو


        classe objet;

        objet=new classe(arg1,arg2, ... argn);

عندما تحتوي فئة ما على منشئ واحد أو أكثر، يجب استخدام أحد هذه المنشئات لإنشاء كائن من هذه الفئة. إذا لم تحتوي فئة C على أي منشئ، فإنها تحتوي على منشئ افتراضي وهو المنشئ بدون معلمات: public C(). يتم عندئذ تهيئة سمات الكائن بقيم افتراضية. وهذا ما حدث عندما كتبنا في البرامج السابقة:

    personne p1;
    p1=new personne();

لنقم بإنشاء منشئين لفصلنا personne:


public class personne{
  // السمات
  private String prenom;
  private String nom;
  private int age;
  
  // منشئات
  public personne(String P, String N, int age){
    initialise(P,N,age);
  }
  public personne(personne P){
    initialise(P);
  }

  // طريقة
  public void initialise(String P, String N, int age){
    this.prenom=P;
    this.nom=N;
    this.age=age;
  }
  public void initialise(personne P){
    this.prenom=P.prenom;
    this.nom=P.nom;
    this.age=P.age;
  }

  // طريقة
  public void identifie(){
    System.out.println(prenom+","+nom+","+age);
  }
}

يكتفي مصنّعانا بالاستعانة بالطرق initialise المقابلة. نذكّر أنه عندما نجد في مصنّع، على سبيل المثال، الترميز initialise(P)، فإن المُجمّع يُترجم إلى this.initialise(P). في المنشئ، يتم استدعاء الطريقة initialise للعمل على الكائن المشار إليه بواسطة this، أي الكائن الحالي، الذي يجري إنشاؤه.

فيما يلي برنامج اختبار:


// استيراد شخص؛
import java.io.*;

public class test1{
  public static void main(String arg[]){
    personne p1=new personne("Jean","Dupont",30);
    System.out.print("p1=");
    p1.identifie();
    personne p2=new personne(p1);
    System.out.print("p2=");
    p2.identifie();
  }
}

والنتائج التي تم الحصول عليها:

p1=Jean,Dupont,30
p2=Jean,Dupont,30

3.1.9. مراجع الكائنات

ما زلنا نستخدم نفس الفئة personne. يصبح برنامج الاختبار كما يلي:


// استيراد شخص؛
import java.io.*;

public class test1{
  public static void main(String arg[]){
    // p1
    personne p1=new personne("Jean","Dupont",30);
    System.out.print("p1=");  p1.identifie();
    // p2 تشير إلى نفس الكائن الذي تشير إليه p1
    personne p2=p1;
    System.out.print("p2="); p2.identifie();
    // p3 يشير إلى كائن سيكون نسخة من الكائن الذي يشير إليه p1
    personne p3=new personne(p1);
    System.out.print("p3=");  p3.identifie();
    // نقوم بتغيير حالة الكائن المشار إليه بواسطة p1
    p1.initialise("Micheline","Benoît",67);
    System.out.print("p1=");  p1.identifie();
    // نظرًا لأن p2=p1، فإن الكائن المشار إليه بواسطة p2 لا بد أن يكون قد تغيرت حالته
    System.out.print("p2=");  p2.identifie();
    // نظرًا لأن p3 لا يشير إلى نفس الكائن الذي يشير إليه p1، فلا بد أن الكائن المشار إليه بواسطة p3 لم يتغير
    System.out.print("p3=");  p3.identifie();
  }
}

النتائج التي تم الحصول عليها هي كما يلي:

p1=Jean,Dupont,30
p2=Jean,Dupont,30
p3=Jean,Dupont,30
p1=Micheline,Benoît,67
p2=Micheline,Benoît,67
p3=Jean,Dupont,30

عند تعريف المتغير p1 بواسطة

personne p1=new personne("Jean","Dupont",30);

p1 يشير إلى الكائن personne("Jean","Dupont",30) ولكنه ليس الكائن نفسه. في لغة C، يمكن القول إنه مؤشر، c.a.d. عنوان الكائن الذي تم إنشاؤه. إذا كتبنا بعد ذلك:

    p1=null

ليس الكائن personne("Jean","Dupont",30) هو الذي يتم تعديله، بل المرجع p1 هو الذي تتغير قيمته. وسيُفقد الكائن person("Jean","Dupont",30) إذا لم تكن هناك متغيرات أخرى تشير إليه.

عندما نكتب:

personne p2=p1;

نقوم بتهيئة المؤشر p2: فهو "يشير" إلى نفس الكائن (يحدد نفس الكائن) الذي يشير إليه المؤشر p1. وبالتالي، إذا قمنا بتعديل الكائن "المشار إليه" (أو المشار إليه) بواسطة p1، فإننا نقوم بتعديل الكائن المشار إليه بواسطة p2.

عندما نكتب:

personne p3=new personne(p1);

يتم إنشاء كائن جديد، وهو نسخة من الكائن المشار إليه بواسطة p1. سيتم الإشارة إلى هذا الكائن الجديد بواسطة p3. إذا قمنا بتعديل الكائن "المشار إليه" (أو المشار إليه) بواسطة p1، فلن نعدل بأي شكل من الأشكال الكائن المشار إليه بواسطة p3. وهذا ما تظهره النتائج التي تم الحصول عليها.

3.1.10. الكائنات المؤقتة

في تعبير ما، يمكننا استدعاء منشئ كائن بشكل صريح: يتم إنشاء هذا الكائن، لكن لا يمكننا الوصول إليه (لتعديله على سبيل المثال). يتم إنشاء هذا الكائن المؤقت لتلبية احتياجات تقييم التعبير ثم يتم التخلي عنه. سيتم استرداد مساحة الذاكرة التي كان يشغلها تلقائيًا لاحقًا بواسطة برنامج يسمى "ramasse-miettes" (مجمع القمامة) وتتمثل مهمته في استرداد مساحة الذاكرة التي تشغلها الكائنات التي لم تعد مرجعية لها في بيانات البرنامج.

لنأخذ المثال التالي:


// استيراد شخص؛

public class test1{
  public static void main(String arg[]){
    new personne(new personne("Jean","Dupont",30)).identifie();
  }
}

ونقوم بتعديل منشئات الفئة personne بحيث تعرض رسالة:


// الشركات المصنعة
  public personne(String P, String N, int age){
    System.out.println("Constructeur personne(String, String, int)");
    initialise(P,N,age);
  }
  public personne(personne P){
    System.out.println("Constructeur personne(personne)");
    initialise(P);
  }

نحصل على النتائج التالية:

Constructeur personne(String, String, int)
Constructeur personne(personne)
Jean,Dupont,30

مما يوضح البناء المتتالي للكائنين المؤقتين.

3.1.11. طرق قراءة وكتابة السمات الخاصة

نضيف إلى الفئة personne الطرق اللازمة لقراءة أو تعديل حالة سمات الكائنات:


public class personne{
  private String prenom;
  private String nom;
  private int age;
  
  public personne(String P, String N, int age){
    this.prenom=P;
    this.nom=N;
    this.age=age;
  }

  public personne(personne P){
    this.prenom=P.prenom;
    this.nom=P.nom;
    this.age=P.age;
  }

  public void identifie(){
    System.out.println(prenom+","+nom+","+age);
  }

  // معدلو
  public String getPrenom(){
    return prenom;
  }
  public String getNom(){
    return nom;
  }
  public int getAge(){
    return age;
  }

  //المعدلون
  public void setPrenom(String P){
    this.prenom=P;
  }
  public void setNom(String N){
    this.nom=N;
  }
  public void setAge(int age){
    this.age=age;
  }
}

نقوم باختبار الفئة الجديدة باستخدام البرنامج التالي:


// استيراد شخص؛

public class test1{
  public static void main(String[] arg){
    personne P=new personne("Jean","Michelin",34);
    System.out.println("P=("+P.getPrenom()+","+P.getNom()+","+P.getAge()+")");
    P.setAge(56);
    System.out.println("P=("+P.getPrenom()+","+P.getNom()+","+P.getAge()+")");
  }
}

ونحصل على النتائج التالية:

P=(Jean,Michelin,34)
P=(Jean,Michelin,56)

3.1.12. طرق وسمات الفئة

لنفترض أننا نريد حساب عدد الكائنات personne التي تم إنشاؤها في أحد التطبيقات. يمكننا إدارة عداد بأنفسنا، لكننا قد ننسى الكائنات المؤقتة التي يتم إنشاؤها هنا أو هناك. يبدو أنه من الأفضل تضمين تعليمة تزيد العداد في منشئات الفئة personne. تكمن المشكلة في تمرير مرجع لهذا العداد حتى يتمكن المنشئ من زيادته: يجب تمرير معلمة جديدة إليهم. يمكن أيضًا تضمين العداد في تعريف الفئة. نظرًا لأنه سمة للفئة نفسها وليس لكائن معين من هذه الفئة، يتم إعلانه بشكل مختلف باستخدام الكلمة الرئيسية static:

    private static long nbPersonnes;        // عدد الأشخاص الذين تم إنشاؤهم

للإشارة إليه، نكتب personne.nbPersonnes لإظهار أنه سمة للفئة personne نفسها. هنا، أنشأنا سمة خاصة لن نتمكن من الوصول إليها مباشرة خارج الفئة. لذلك، نقوم بإنشاء طريقة عامة للوصول إلى سمة الفئة nbPersonnes. لجعل قيمة nbPersonnes، لا تحتاج الطريقة إلى كائن معين: في الواقع، nbPersonnes ليس سمة لكائن معين، بل هو سمة لفئة بأكملها. لذلك نحتاج إلى طريقة فئة مُعلنة أيضًا باسم static:

public static long getNbPersonnes(){
    return nbPersonnes;
}

والتي سيتم استدعاؤها من الخارج باستخدام الصيغة personne.getNbPersonnes(). فيما يلي مثال على ذلك.

تصبح الفئة personne كما يلي:


public class personne{
  
  // سمة الفئة
  private static long nbPersonnes=0;

  // سمات الكائنات

  
  // المنشئات
  public personne(String P, String N, int age){
    initialise(P,N,age);
    nbPersonnes++;
  }
  public personne(personne P){
    initialise(P);
    nbPersonnes++;
  }

  // طريقة


  // طريقة الفئة
  public static long getNbPersonnes(){
    return nbPersonnes;
  }

}// فئة

باستخدام البرنامج التالي:


// استيراد شخص؛

public class test1{
  public static void main(String arg[]){
    personne p1=new personne("Jean","Dupont",30);
    personne p2=new personne(p1);
    new personne(p1);
    System.out.println("Nombre de personnes créées : "+personne.getNbPersonnes());
  }// الرئيسية
}//اختبار1

نحصل على النتائج التالية:

    Nombre de personnes créées : 3

3.1.13. تمرير كائن إلى دالة

سبق أن ذكرنا أن Java تنقل المعلمات الفعلية للدالة بالقيمة: حيث يتم نسخ قيم المعلمات الفعلية إلى المعلمات الشكلية. وبالتالي، لا يمكن للدالة تعديل المعلمات الفعلية.

في حالة الكائن، لا يجب أن ننخدع بالاستخدام الخاطئ للغة الذي يحدث بشكل منهجي عند الحديث عن الكائن بدلاً من مرجع الكائن. لا يتم التعامل مع الكائن إلا من خلال مرجع (مؤشر) عليه. وبالتالي، فإن ما يتم تمريره إلى الدالة ليس الكائن نفسه بل مرجعًا على هذا الكائن. وبالتالي، فإن قيمة المرجع وليس قيمة الكائن نفسه هي التي يتم نسخها في المعلمة الشكلية: لا يتم إنشاء كائن جديد.

إذا تم تمرير مرجع الكائن R1 إلى دالة، فسيتم نسخه في المعلمة الشكلية المقابلة R2. وبالتالي، تشير المرجعان R2 و R1 إلى نفس الكائن. إذا قامت الدالة بتعديل الكائن الذي يشير إليه R2، فإنها تعدل بالطبع الكائن الذي يشير إليه R1 لأنه هو نفسه.

Image

وهذا ما يوضحه المثال التالي:


// استيراد شخص؛

public class test1{
  public static void main(String arg[]){
    personne p1=new personne("Jean","Dupont",30);
    System.out.print("Paramètre effectif avant modification : ");
    p1.identifie();
    modifie(p1);
    System.out.print("Paramètre effectif après modification : ");
    p1.identifie();
  }// الرئيسية

  private static void modifie(personne P){
    System.out.print("Paramètre formel avant modification : ");
    P.identifie();
    P.initialise("Sylvie","Vartan",52);
    System.out.print("Paramètre formel après modification : ");
    P.identifie();
  }// تعديل
}// فئة

يتم الإعلان عن الطريقة modifie باسم static لأنها طريقة فئة: لا يلزم وضع بادئة كائن أمامها لاستدعائها. النتائج التي تم الحصول عليها هي كما يلي:

Constructeur personne(String, String, int)
Paramètre effectif avant modification : Jean,Dupont,30
Paramètre formel avant modification : Jean,Dupont,30
Paramètre formel après modification : Sylvie,Vartan,52
Paramètre effectif après modification : Sylvie,Vartan,52

نرى أنه لم يتم إنشاء سوى كائن واحد: كائن الشخص p1 الخاص بالوظيفة main، وأن الكائن قد تم تعديله بالفعل بواسطة الوظيفة modifie.

3.1.14. تغليف معلمات الإخراج لدالة في كائن

نظرًا لتمرير المعلمات بالقيمة، لا يمكن كتابة دالة في Java تحتوي على معلمات إخراج من النوع int على سبيل المثال، لأننا لا نستطيع تمرير مرجع لنوع int الذي لا يمثل كائنًا. يمكننا إذن إنشاء فئة تغلف النوع int:


public class entieres{
  private int valeur;

  public entieres(int valeur){
    this.valeur=valeur;
  }

  public void setValue(int valeur){
    this.valeur=valeur;
  }

  public int getValue(){
    return valeur;
  }
}

تحتوي الفئة السابقة على منشئ يسمح بتهيئة عدد صحيح وطريقتين تسمحان بقراءة وتعديل قيمة هذا العدد الصحيح. نختبر هذه الفئة باستخدام البرنامج التالي:


// استيراد أعداد صحيحة;

public class test2{
  public static void main(String[] arg){
    entieres I=new entieres(12);
    System.out.println("I="+I.getValue());
    change(I);
    System.out.println("I="+I.getValue());
  }
  private static void change(entieres entier){
    entier.setValue(15);
  }
}

ونحصل على النتائج التالية:

I=12
I=15

3.1.15. جدول بالأشخاص

الكائن هو بيانات مثل أي بيانات أخرى، وبهذا الصفة يمكن تجميع عدة كائنات في مصفوفة:


// استيراد شخص؛

public class test1{
  public static void main(String arg[]){
    personne[] amis=new personne[3];
    System.out.println("----------------");
    amis[0]=new personne("Jean","Dupont",30);
    amis[1]=new personne("Sylvie","Vartan",52);
    amis[2]=new personne("Neil","Armstrong",66);
    int i;
    for(i=0;i<amis.length;i++)
      amis[i].identifie();
  }
}

تقوم التعليمات personne[] amis=new personne[3]; بإنشاء مصفوفة من 3 عناصر من النوع personne. يتم تهيئة هذه العناصر الثلاثة هنا بالقيم null و c.a.d، وهي لا تشير إلى أي كائن. ومرة أخرى، وبسبب سوء استخدام المصطلحات، نتحدث عن مصفوفة كائنات في حين أنها مجرد مصفوفة من مراجع الكائنات. إن إنشاء مصفوفة الكائنات، وهي مصفوفة تعد كائنًا بحد ذاتها (وجود new)، لا يؤدي في حد ذاته إلى إنشاء أي كائن من نوع عناصرها: يجب القيام بذلك لاحقًا.

ونحصل على النتائج التالية:

----------------
Constructeur personne(String, String, int)
Constructeur personne(String, String, int)
Constructeur personne(String, String, int)
Jean,Dupont,30
Sylvie,Vartan,52
Neil,Armstrong,66

3.2. التوريث بالقدوة

3.2.1. مقدمة

نتناول هنا مفهوم التوريث. الهدف من التوريث هو "تخصيص" فئة موجودة لتلبية احتياجاتنا. لنفترض أننا نريد إنشاء فئة enseignant: المعلم هو شخص معين. لديه سمات لا يمتلكها أي شخص آخر: المادة التي يدرسها على سبيل المثال. ولكنه يمتلك أيضًا سمات أي شخص: الاسم الأول واسم العائلة والعمر. وبالتالي، فإن المعلم ينتمي بالكامل إلى الفئة personne ولكنه يمتلك سمات إضافية. بدلاً من كتابة فئة enseignant من الصفر، نفضل الاستفادة من مكونات فئة personne وتكييفها مع السمات الخاصة بالمعلمين. وهذا ما يتيحه لنا مفهوم التوريث.

للتعبير عن أن الفئة enseignant ترث خصائص الفئة personne، سنكتب:


    public class enseignant extends personne

تسمى personne بالفئة الأصلية (أو الأم) وتسمى enseignant بالفئة المشتقة (أو الابنة). يتمتع الكائن enseignant بجميع خصائص الكائن personne: فهو يمتلك نفس السمات ونفس الأساليب. لا تتكرر هذه السمات والأساليب الخاصة بالفئة الأم في تعريف الفئة الفرعية: نكتفي بالإشارة إلى السمات والأساليب التي أضافتها الفئة الفرعية:


class enseignant extends personne{
// السمات
  private int section;

// منشئ
  public enseignant(String P, String N, int age,int section){
    super(P,N,age);
    this.section=section;
  }
}

نفترض أن الفئة personne محددة على النحو التالي:


public class personne{
  private String prenom;
  private String nom;
  private int age;
  
  public personne(String P, String N, int age){
    this.prenom=P;
    this.nom=N;
    this.age=age;
  }

  public personne(personne P){
    this.prenom=P.prenom;
    this.nom=P.nom;
    this.age=P.age;
  }

  public String identite(){
    return "personne("+prenom+","+nom+","+age+")";
  }

  // الوصول
  public String getPrenom(){
    return prenom;
  }
  public String getNom(){
    return nom;
  }
  public int getAge(){
    return age;
  }

  //المعدلات
  public void setPrenom(String P){
    this.prenom=P;
  }
  public void setNom(String N){
    this.nom=N;
  }
  public void setAge(int age){
    this.age=age;
  }
}

تم تعديل الطريقة identifie بشكل طفيف لتقديم سلسلة أحرف تحدد هوية الشخص، وهي تحمل الآن الاسم identite. هنا تضيف الفئة enseignant إلى طرق وسمات الفئة personne:

  • سمة section وهي رقم القسم الذي ينتمي إليه المعلم ضمن هيئة التدريس (قسم لكل تخصص بشكل عام)
  • منشئ جديد يسمح بتهيئة جميع سمات المعلم

3.2.2. إنشاء كائن مدرس

منشئ الفئة enseignant هو التالي:


// منشئ
  public enseignant(String P, String N, int age,int section){
    super(P,N,age);
    this.section=section;
  }

التعليمات super(P,N,age) هي استدعاء لمنشئ الفئة الأصلية، وهي في هذه الحالة الفئة personne. نعلم أن هذا المنشئ يقوم بتهيئة الحقول prenom و nom و age للكائن personne الموجود داخل الكائن étudiant. يبدو هذا معقدًا للغاية وقد نفضل كتابة:


// منشئ
  public enseignant(String P, String N, int age,int section){
    this.prenom=P;
        this.nom=N
        this.age=age
      this.section=section;
  }

هذا مستحيل. فقد أعلنت الفئة personne أن حقولها الثلاثة prenom و nom و age هي حقول خاصة (private). فقط الكائنات من نفس الفئة لها وصول مباشر إلى هذه الحقول. يجب على جميع الكائنات الأخرى، بما في ذلك الكائنات الفرعية كما في هذه الحالة، المرور عبر طرق عامة للوصول إليها. كان الأمر سيختلف لو أن الفئة personne أعلنت الحقول الثلاثة محمية (protected): كانت ستسمح عندئذٍ للفئات المشتقة بالوصول المباشر إلى الحقول الثلاثة. في مثالنا، كان استخدام منشئ الفئة الأم هو الحل الصحيح، وهذه هي الطريقة المعتادة: عند إنشاء كائن فرعي، نستدعي أولاً منشئ الكائن الأم ثم نكمل عمليات التهيئة الخاصة بالكائن الفرعي (section في مثالنا).

لنجرب برنامجًا أوليًا:


// استيراد شخص؛
// استيراد معلم؛

public class test1{
  public static void main(String arg[]){
    System.out.println(new enseignant("Jean","Dupont",30,27).identite());
  }
}

يكتفي هذا البرنامج بإنشاء كائن enseignant (جديد) وتحديده. لا تحتوي الفئة enseignant على طريقة identité، لكن فئتها الأم تحتوي على طريقة، وهي طريقة عامة: تصبح هذه الطريقة، عن طريق التوريث، طريقة عامة للفئة enseignant.

يتم تجميع ملفات مصدر الفئات في نفس الدليل ثم يتم تجميعها:

E:\data\serge\JAVA\BASES\OBJETS\4>dir
10/06/2002  10:00                  765 personne.java
10/06/2002  10:00                  212 enseignant.java
10/06/2002  10:01                  192 test1.java

E:\data\serge\JAVA\BASES\OBJETS\4>javac *.java

E:\data\serge\JAVA\BASES\OBJETS\4>dir
10/06/2002  10:00                  765 personne.java
10/06/2002  10:00                  212 enseignant.java
10/06/2002  10:01                  192 test1.java
10/06/2002  10:02                  316 enseignant.class
10/06/2002  10:02                1 146 personne.class
10/06/2002  10:02                  550 test1.class

يتم تنفيذ الملف test1.class:

E:\data\serge\JAVA\BASES\OBJETS\4>java test1
personne(Jean,Dupont,30)

3.2.3. تحميل طريقة

في المثال السابق، حصلنا على هوية الجزء personne الخاص بالمدرس، ولكن هناك بعض المعلومات الخاصة بالفصل enseignant (القسم) غير موجودة. لذلك، يتعين علينا كتابة طريقة تسمح بتحديد هوية المدرس:


class enseignant extends personne{
  int section;

  public enseignant(String P, String N, int age,int section){
    super(P,N,age);
    this.section=section;
  }

  public String identite(){
    return "enseignant("+super.identite()+","+section+")";    
  }    
}

تعتمد الطريقة identite للفئة enseignant على الطريقة identite لفئتها الأم (super.identite) لعرض جزءها "personne" ثم تكمله بالحقل section الخاص بالفئة enseignant.

تحتوي الفئة enseignant الآن على طريقتين identite:

  • الطريقة الموروثة من الفئة الأم personne
  • والطريقة الخاصة بها

إذا كان E كائنًا من نوع enseignant، فإن E.identite تشير إلى الطريقة identite للفئة enseignant. يُقال إن الطريقة identite للفئة الأم "مُثبَّتة" بواسطة الطريقة identite للفئة الفرعية. بشكل عام، إذا كان O كائنًا وM طريقة، لتنفيذ الطريقة O.M، يبحث النظام عن طريقة M بالترتيب التالي:

  • في فئة الكائن O
  • في فئته الأم إذا كان له فئة أم
  • في الفئة الأم لفئته الأم إن وجدت
  • إلخ...

وبالتالي، فإن الوراثة تسمح بتحميل الطرق التي تحمل نفس الاسم في الفئة الأم في الفئة الفرعية. وهذا ما يسمح بتكييف الفئة الفرعية مع احتياجاتها الخاصة. وبالاقتران مع تعدد الأشكال الذي سنراه لاحقًا، فإن تحميل الطرق هو الميزة الرئيسية للوراثة.

لنأخذ نفس المثال السابق:


// استيراد شخص؛
// استيراد معلم؛

public class test1{
  public static void main(String arg[]){
    System.out.println(new enseignant("Jean","Dupont",30,27).identite());
  }
}

النتائج التي تم الحصول عليها هذه المرة هي التالية:

    enseignant(personne(Jean,Dupont,30),27)

3.2.4. التعدد الشكلي

لنأخذ سلسلة من الفئات: C0C1C2 … Cn

حيث CiCj تشير إلى أن الفئة Cj مشتقة من الفئة Ci. وهذا يعني أن الفئة Cj تمتلك جميع خصائص الفئة Ci بالإضافة إلى خصائص أخرى. لنفترض أن هناك كائنات Oi من النوع Ci. من المسموح كتابة:

    Oi=Oj avec j>i

في الواقع، من خلال التوريث، تمتلك الفئة Cj جميع خصائص الفئة Ci بالإضافة إلى خصائص أخرى. وبالتالي، فإن الكائن Oj من النوع Cj يحتوي في داخله على كائن من النوع Ci. العملية

    Oi=Oj

تجعل Oi مرجعًا للكائن من النوع Ci الموجود داخل الكائن Oj.

حقيقة أنالمتغير Oi من الفئة Ci يمكنه في الواقع الإشارة ليس فقط إلى كائن من الفئة Ci بل إلى أي كائن مشتق من الفئة Ci يُسمى تعدد الأشكال: قدرة متغير على الإشارة إلى أنواع مختلفة من الكائنات.

لنأخذ مثالاً وننظر إلى الدالة التالية المستقلة عن أي فئة:

    public static void affiche(Object obj){
        .
    }

الفئة Object هي "الأم" لجميع فئات Java. لذلك عندما نكتب:

    public class personne

فإننا نكتب ضمناً:

    public class personne extends Object

وبالتالي، فإن كل كائن Java يحتوي في داخله على جزء من النوع Object. وبالتالي، يمكننا كتابة:

    enseignant e;
    affiche(e);

ستتلقى المعلمة الشكلية من النوع Object للدالة affiche قيمة من النوع enseignant. وبما أن enseignant مشتق من Object، فإن ذلك يعتبر صحيحًا.

3.2.5. التحميل الزائد والتعدد

لنكمل دالتنا affiche:

    public static void affiche(Object obj){
        System.out.println(obj.toString());
    }

تُرجع الدالة obj.toString() سلسلة أحرف تحدد الكائن obj بالصيغة nom_de_la_classe@adresse_de_l'الكائن. ماذا يحدث في حالة المثال السابق:

    enseignant e=new enseignant(...);
    affiche(e);

سيتعين على النظام تنفيذ التعليمات System.out.println(e.toString()) حيث e هو كائن معلم. سيبحث عن طريقة toString في التسلسل الهرمي للفئات المؤدية إلى الفئة enseignant بدءًا من الأخيرة:

  • في الفئة enseignant، لا يجد طريقة toString()
  • في الفئة الأم personne، لا يجد طريقة toString()
  • في الفئة الأم Object، يجد الطريقة toString() ويقوم بتنفيذها

وهذا ما يوضحه البرنامج التالي:


// استيراد شخص؛
// استيراد معلم؛

public class test1{
  public static void main(String arg[]){
    enseignant e=new enseignant("Lucile","Dumas",56,61);
    affiche(e);
    personne p=new personne("Jean","Dupont",30);
    affiche(p);
  }
  public static void affiche(Object obj){
    System.out.println(obj.toString());
  }
}

النتائج التي تم الحصول عليها هي التالية:

enseignant@1ee789
personne@1ee770

أي nom_de_la_classe@adresse_de_l الكائن. ونظرًا لأن هذا ليس واضحًا تمامًا، فقد نميل إلى تعريف طريقة toString للفئتين personne و etudiant التي ستقوم بتحميل الطريقة toString للفئة الأم Object. بدلاً من كتابة طرق قد تكون مشابهة لطرق identite الموجودة بالفعل في الفئتين personne و enseignant، فلتكتفِ بإعادة تسمية هذه الطرق identite إلى toString:


public class personne{
    ...

  public String toString(){
    return "personne("+prenom+","+nom+","+age+")";
  }

    ...
}

class enseignant extends personne{
  int section;



  public String toString(){
    return "enseignant("+super.toString()+","+section+")";    
  }    
}

باستخدام نفس برنامج الاختبار السابق، تم الحصول على النتائج التالية:

enseignant(personne(Lucile,Dumas,56),61)
personne(Jean,Dupont,30)

3.3. الفئات الداخلية

يمكن أن تحتوي فئة ما على تعريف فئة أخرى. لنأخذ المثال التالي:

// الفصول المستوردة
import java.io.*;

public class test1{

    // فئة داخلية
    private class article{
         // نحدد البنية
        private String code;
        private String nom;
        private double prix;
        private int stockActuel;
        private int stockMinimum;

     // منشئ
    public article(String code, String nom, double prix, int stockActuel, int stockMinimum){
         // تهيئة السمات
      this.code=code;
      this.nom=nom;
      this.prix=prix;
      this.stockActuel=stockActuel;
      this.stockMinimum=stockMinimum;
    }//منشئ

    //toString
    public String toString(){
        return "article("+code+","+nom+","+prix+","+stockActuel+","+stockMinimum+")";
    }//toString
  }//فئة المادة

   // بيانات محلية
  private article art=null;

   // الشركة المصنعة
  public test1(String code, String nom, double prix, int stockActuel, int stockMinimum){
       // تعريف السمة
    art=new article(code, nom, prix, stockActuel,stockMinimum);
  }//اختبار1

   // أداة الوصول
  public article getArticle(){
      return art;
  }//getArticle

    public static void main(String arg[]){
      // إنشاء مثيل test1
      test1 t1=new test1("a100","velo",1000,10,5);
    // عرض test1.art
    System.out.println("art="+t1.getArticle());
  }//الرئيسي

}// نهاية الفئة

تحتوي الفئة test1 على تعريف فئة أخرى، وهي الفئة article. يُقال إن article هي فئة داخلية في الفئة test1. قد يكون هذا مفيدًا عندما لا تكون الفئة الداخلية مفيدة إلا في الفئة التي تحتوي عليها. عند ترجمة المصدر test1.java أعلاه، نحصل على ملفين .class:

E:\data\serge\JAVA\classes\interne>dir
05/06/2002  17:26                1 362 test1.java
05/06/2002  17:26                  941 test1$article.class
05/06/2002  17:26                1 020 test1.class

تم إنشاء ملف test1$article.class للفئة article الداخلية للفئة test1. عند تشغيل البرنامج أعلاه، نحصل على النتائج التالية:

E:\data\serge\JAVA\classes\interne>java test1
art=article(a100,velo,1000.0,10,5)

3.4. الواجهات

الواجهة هي مجموعة من نماذج أولية للطرق أو الخصائص التي تشكل عقدًا. تلتزم الفئة التي تقرر تنفيذ واجهة بتوفير تنفيذ لجميع الطرق المحددة في الواجهة. ويقوم المُجمع بالتحقق من هذا التنفيذ.

فيما يلي، على سبيل المثال، تعريف الواجهة java.util.Enumeration:

ملخص الطريقة
 
boolean
hasMoreElements()
          يختبر ما إذا كان هذا التعداد يحتوي على المزيد من العناصر.
 
كائن
nextElement()
          تُرجع العنصر التالي في هذا التعداد إذا كان كائن التعداد هذا يحتوي على عنصر واحد على الأقل لتقديمه.
 

سيتم إعلان أي فئة تنفذ هذه الواجهة على أنها

public class C : Enumeration{
    ...
    boolean hasMoreElements(){....}
    Object nextElement(){...}
}

يجب تعريف الطريقتين hasMoreElements() و nextElement() في الفئة C.

لننظر إلى الكود التالي الذي يحدد فئة élève التي تحدد اسم الطالب ودرجته في مادة ما:


     // فئة تلميذ
public class élève{
     // السمات العامة
    public String nom;
    public double note;
     // منشئ
    public élève(String NOM, double NOTE){
        nom=NOM;
        note=NOTE;
    }//منشئ
}//تلميذ  

نقوم بتعريف فئة notes تجمع درجات جميع الطلاب في مادة ما:


// فئات مستوردة
// استيراد الطالب

// فئة الدرجات
public class notes{

     // السمات
    protected String matière;
    protected élève[] élèves;

     // منشئ
    public notes (String MATIERE, élève[] ELEVES){
         // تخزين الطلاب والمواد
        matière=MATIERE;
        élèves=ELEVES;
    }//الدرجات

    // toString
    public String toString(){
        String valeur="matière="+matière +", notes=(";
        int i;
         // نقوم بدمج جميع الدرجات
        for (i=0;i<élèves.length-1;i++){
            valeur+="["+élèves[i].nom+","+élèves[i].note+"],";
        };
         //آخر علامة
        if(élèves.length!=0){ valeur+="["+élèves[i].nom+","+élèves[i].note+"]";}
        valeur+=")";
         // النهاية
        return valeur;
    }//toString
}//الفئة

تم تعريف السمتين matière و élèves على أنهما protected ليتم الوصول إليهما من فئة مشتقة. قررنا اشتقاق الفئة notes في فئة notesStats التي ستحتوي على سمتين إضافيتين، وهما متوسط الدرجات والانحراف المعياري للدرجات:


public class notesStats extends notes implements Istats {
     // السمات
    private double _moyenne;
    private double _écartType;

تستمد الفئة notesStats من الفئة notes وتنفذ الواجهة Istats التالية:


// واجهة
public interface Istats{
    double moyenne();
    double écartType();
}//

وهذا يعني أن الفئة notesStats يجب أن تحتوي على طريقتين تسميان moyenne و écartType مع التوقيع المحدد في الواجهة Istats. الفئة notesStats هي كما يلي:


// الفئات المستوردة
// استيراد ملاحظات؛
// استيراد Istats؛
// استيراد élève;

public class notesStats extends notes implements Istats {
     // السمات
    private double _moyenne;
    private double _écartType;

     // منشئ
    public notesStats (String MATIERE, élève[] ELEVES){
         // إنشاء الفئة الأم
        super(MATIERE,ELEVES);
         // حساب متوسط الدرجات
        double somme=0;
        for (int i=0;i<élèves.length;i++){
            somme+=élèves[i].note;
        }
        if(élèves.length!=0) _moyenne=somme/élèves.length;
        else _moyenne=-1;
         // الانحراف المعياري
        double carrés=0;
        for (int i=0;i<élèves.length;i++){
            carrés+=Math.pow((élèves[i].note-_moyenne),2);
        }//for
        if(élèves.length!=0) _écartType=Math.sqrt(carrés/élèves.length);
        else _écartType=-1;
    }//منشئ

    
     // ToString
    public String toString(){
        return super.toString()+",moyenne="+_moyenne+",écart-type="+_écartType;
    }//ToString

     // طرق واجهة Istats
    public double moyenne(){
         // يحسب متوسط الدرجات
        return _moyenne;
    }//المتوسط
    public double écartType(){
         // يعطي الانحراف المعياري
        return _écartType;
    }//écartType
}//الفئة

يتم حساب المتوسط _moyenne والانحراف المعياري _ecartType فور إنشاء الكائن. لذلك، لا يتعين على الطريقتين moyenne و écartType سوى إرجاع قيمة السمتين _moyenne و _ecartType. ترجع الطريقتان -1 إذا كان جدول الطلاب فارغًا.

فئة الاختبار التالية:


// الفئات المستوردة
// استيراد الطالب؛
// استيراد Istats؛
// استيراد الدرجات؛
// استيراد notesStats؛

// فصل الاختبار
public class test{
    public static void main(String[] args){
        // بعض الطلاب والدرجات
        élève[] ELEVES=new élève[] { new élève("paul",14),new élève("nicole",16), new élève("jacques",18)};
         // يتم تسجيلها في كائن الملاحظات
        notes anglais=new notes("anglais",ELEVES);
         // ويتم عرضها
        System.out.println(""+anglais);
         // نفس الشيء مع المتوسط والانحراف المعياري
        anglais=new notesStats("anglais",ELEVES);
        System.out.println(""+anglais);
    }//يدوي
}//الفصل

يعطي النتائج:


matière=anglais, notes=([paul,14.0],[nicole,16.0],[jacques,18.0])
matière=anglais, notes=([paul,14.0],[nicole,16.0],[jacques,18.0]),moyenne=16.0,écart-type=1.632993161855452

تخضع جميع الفئات المختلفة في هذا المثال لملف مصدر مختلف:

E:\data\serge\JAVA\interfaces\notes>dir
06/06/2002  14:06                  707 notes.java
06/06/2002  14:06                  878 notes.class
06/06/2002  14:07                1 160 notesStats.java
06/06/2002  14:02                  101 Istats.java
06/06/2002  14:02                  138 Istats.class
06/06/2002  14:05                  247 élève.java
06/06/2002  14:05                  309 élève.class
06/06/2002  14:07                1 103 notesStats.class
06/06/2002  14:10                  597 test.java
06/06/2002  14:10                  931 test.class

كان من الممكن أن تقوم الفئة notesStats بتنفيذ الطرق moyenne و écartType لنفسها دون الإشارة إلى أنها تنفذ الواجهة Istats. إذن ما فائدة الواجهات؟ الفائدة هي التالية: يمكن أن تقبل الدالة كمعلمة بيانات من نوع واجهة I. عندئذٍ يمكن لأي كائن من فئة C التي تنفذ الواجهة I أن يكون معلمة لهذه الدالة. لنفكر في الواجهة التالية:


// واجهة Iexample
public interface Iexemple{
    int ajouter(int i,int j);
    int soustraire(int i,int j);
}//واجهة

تحدد الواجهة Iexemple طريقتين هما ajouter و soustraire. وتقوم الفئتان التاليتان classe1 و classe2 بتنفيذ هذه الواجهة.


// الفئات المستوردة
// استيراد Iexample؛

public class classe1 implements Iexemple{
public int ajouter(int a, int b){
        return a+b+10;
    }
    public int soustraire(int a, int b){
        return a-b+20;
    }
}//فئة

// فئات مستوردة
// استيراد Iexemple;

public class classe2 implements Iexemple{
    public int ajouter(int a, int b){
                    return a+b+100;
                }
    public int soustraire(int a, int b){
        return a-b+200;
    }
}//فئة

لتبسيط المثال، لا تقوم الفئات بأي شيء سوى تنفيذ واجهة Iexemple. الآن لننظر إلى المثال التالي:


// الفئات المستوردة
// استيراد فئة1؛
// استيراد فئة2؛

// فئة الاختبار
public class test{
    // دالة ثابتة
    private static void calculer(int i, int j, Iexemple inter){
        System.out.println(inter.ajouter(i,j));
        System.out.println(inter.soustraire(i,j));
    }//حساب

     // دالة main
    public static void main(String[] arg){
        // إنشاء كائنين class1 و class2
        classe1 c1=new classe1();
        classe2 c2=new classe2();
        // استدعاءات الدالة الثابتة calculate
        calculer(4,3,c1);
        calculer(14,13,c2);
    }//main
}//فئة test

تقبل الدالة الثابتة calculer كمعلمة عنصر من النوع Iexemple. وبالتالي، يمكنها أن تستقبل لهذا المعلمة كائنًا من النوع classe1 أو من النوع classe2. وهذا ما يتم في الدالة main مع النتائج التالية:

17
21
127
201

نرى إذن أن لدينا هنا خاصية قريبة من تعدد الأشكال الذي رأيناه بالنسبة للفئات. لذا، إذا كانت مجموعة من الفئات Ci غير مرتبطة ببعضها البعض عن طريق التوريث (وبالتالي لا يمكننا استخدام تعدد الأشكال في التوريث) تقدم مجموعة من الطرق ذات التوقيع نفسه، فقد يكون من المفيد تجميع هذه الطرق في واجهة I ترثها جميع الفئات المعنية. يمكن عندئذٍ استخدام مثيلات هذه الفئات Ci كمعلمات لدوال تقبل معلمة من النوع I، c.a.d. دوال لا تستخدم سوى أساليب الكائنات Ci المحددة في الواجهة I وليس السمات والأساليب الخاصة بالفئات Ci المختلفة.

في المثال السابق، كانت كل فئة أو واجهة موضوع ملف مصدر منفصل:

E:\data\serge\JAVA\interfaces\opérations>dir
06/06/2002  14:33                  128 Iexemple.java
06/06/2002  14:34                  218 classe1.java
06/06/2002  14:32                  220 classe2.java
06/06/2002  14:33                  144 Iexemple.class
06/06/2002  14:34                  325 classe1.class
06/06/2002  14:34                  326 classe2.class
06/06/2002  14:36                  583 test.java
06/06/2002  14:36                  628 test.class

وأخيرًا، تجدر الإشارة إلى أن وراثة الواجهات يمكن أن تكون متعددة، c.a.d. ويمكن كتابة

public class classeDérivée extends classeDeBase implements i1,i2,..,in{
...
}

حيث ij هي واجهات.

3.5. الفئات المجهولة

في المثال السابق، كان من الممكن عدم تعريف الفئتين classe1 و classe2 بشكل صريح. لننظر إلى البرنامج التالي الذي يقوم بنفس الشيء تقريبًا مثل البرنامج السابق ولكن دون التعريف الصريح للفئتين classe1 و classe2:


// الفئات المستوردة
// استيراد Iexemple؛

// فئة الاختبار
public class test2{

  // فئة داخلية
  private static class classe3 implements Iexemple{
        public int ajouter(int a, int b){
            return a+b+1000;
        }
        public int soustraire(int a, int b){
            return a-b+2000;
        }
    };//تعريف الفئة 3

     // دالة ثابتة
    private static void calculer(int i, int j, Iexemple inter){
        System.out.println(inter.ajouter(i,j));
        System.out.println(inter.soustraire(i,j));
    }//حساب

     // دالة main
    public static void main(String[] arg){
        // إنشاء كائنين ينفذان واجهة Iexample
        Iexemple i1=new Iexemple(){
        public int ajouter(int a, int b){
            return a+b+10;
        }
        public int soustraire(int a, int b){
            return a-b+20;
        }
    };//تعريف i1

        Iexemple i2=new Iexemple(){
        public int ajouter(int a, int b){
            return a+b+100;
        }
        public int soustraire(int a, int b){
            return a-b+200;
        }
    };//تعريف i2
        // كائن آخر Iexemple
    Iexemple i3=new classe3();

        // استدعاءات الدالة الثابتة calculate
        calculer(4,3,i1);
        calculer(14,13,i2);
    calculer(24,23,i3);
    }//main
}//فئة test

تكمن الميزة في الكود:


         // إنشاء كائنين ينفذان واجهة Iexample
        Iexemple i1=new Iexemple(){
        public int ajouter(int a, int b){
            return a+b+10;
        }
        public int soustraire(int a, int b){
            return a-b+20;
        }
    };//تعريف i1

يتم إنشاء كائن i1 الذي يتمثل دوره الوحيد في تنفيذ واجهة Iexemple. هذا الكائن من النوع Iexemple. وبالتالي، يمكن إنشاء كائنات من نوع واجهة. تعيد العديد من أساليب فئات Java كائنات من نوع واجهة c.a.d، وهي كائنات لا تهدف سوى إلى تنفيذ أساليب واجهة ما. لإنشاء الكائن i1، قد يميل المرء إلى كتابة:


        Iexemple i1=new Iexemple()

ولكن لا يمكن إنشاء مثيل لواجهة فقط. يمكن إنشاء مثيل لفئة تنفذ هذه الواجهة فقط. هنا، نحدد فئة من هذا النوع "على الفور" في نص تعريف الكائن i1 نفسه:


        Iexemple i1=new Iexemple(){
        public int ajouter(int a, int b){
            // تعريف إضافة
        }
        public int soustraire(int a, int b){
            // تعريف طرح
        }
    };//تعريف i1

معنى هذه التعليمات مشابه للتسلسل:

public class test2{
................
// فئة داخلية
private static class classe1 implements Iexemple{
        public int ajouter(int a, int b){
            // تعريف إضافة
        }
        public int soustraire(int a, int b){
            // تعريف الطرح
        }
};//تعريف الفئة 1
.................
    public static void main(String[] arg){
...........
        Iexemple i1=new classe1();
}//main
}//فئة

في المثال أعلاه، يتم إنشاء مثيل لفئة وليس لواجهة. تسمى الفئة التي يتم تعريفها "على الفور" فئة مجهولة. وهي طريقة تُستخدم غالبًا لإنشاء مثيلات لكائنات لا دور لها سوى تنفيذ واجهة.

يؤدي تنفيذ البرنامج السابق إلى النتائج التالية:

17
21
127
201
1047
2001

استخدم المثال السابق فئات مجهولة لتنفيذ واجهة. ويمكن استخدام هذه الفئات أيضًا لاشتقاق فئات لا تحتوي على منشئات ذات معلمات. لننظر إلى المثال التالي:

// الفئات المستوردة
// استيراد Iexample؛

class classe3 implements Iexemple{
    public int ajouter(int a, int b){
        return a+b+1000;
    }
    public int soustraire(int a, int b){
        return a-b+2000;
    }
};//تعريف الفئة 3

public class test4{

     // دالة ثابتة
    private static void calculer(int i, int j, Iexemple inter){
        System.out.println(inter.ajouter(i,j));
        System.out.println(inter.soustraire(i,j));
    }//حساب

   // طريقة main
    public static void main(String args[]){
       // تعريف فئة مجهولة مشتقة من class3
     // لإعادة تعريف الطرح
    classe3 i1=new classe3(){
        public int ajouter(int a, int b){
          return a+b+10000;
      }//طرح
    };//i1
          // استدعاءات الدالة الثابتة calculate
        calculer(4,3,i1);
  }//main
}//فئة 

نجد هنا فئة classe3 التي تنفذ واجهة Iexemple. في الدالة main، نحدد متغير i1 الذي يكون نوعه فئة مشتقة من classe3. يتم تعريف هذه الفئة المشتقة "على الفور" في فئة مجهولة وتعيد تعريف الطريقة ajouter للفئة classe3. بناء الجملة مطابق لبناء الجملة الخاص بالفئة المجهولة التي تنفذ واجهة. ولكن هنا، يكتشف المُجمِّع أن classe3 ليست واجهة بل فئة. وبالنسبة له، فإنها تمثل اشتقاقًا للفئة. وستحل جميع الطرق التي يجدها في نص الفئة المجهولة محل الطرق التي تحمل نفس الاسم في الفئة الأساسية.

يؤدي تنفيذ البرنامج السابق إلى النتائج التالية:

E:\data\serge\JAVA\classes\anonyme>java test4
10007
2001

3.6. الحزم

3.6.1. إنشاء فئات في حزمة

لكتابة سطر على الشاشة، نستخدم الأمر

System.out.println(...)

إذا نظرنا إلى تعريف الفئة System، نكتشف أنها تسمى في الواقع java.lang.System:

Image

لنتحقق من ذلك باستخدام مثال:


public class test1{
    public static void main(String[] args){
        java.lang.System.out.println("Coucou");
    }//main
}//فئة

لنقوم بتجميع هذا البرنامج وتشغيله:

E:\data\serge\JAVA\classes\paquetages>javac test1.java

E:\data\serge\JAVA\classes\paquetages>dir
06/06/2002  15:40                  127 test1.java
06/06/2002  15:40                  410 test1.class

E:\data\serge\JAVA\classes\paquetages>java test1
Coucou

لماذا إذن يمكننا كتابة


        System.out.println("Coucou");

بدلاً من


        java.lang.System.out.println("Coucou");

لأنه بشكل ضمني، هناك استيراد منهجي لـ"حزمة" java.lang لكل برنامج Java. وبالتالي، فإن كل شيء يسير كما لو كان لدينا في بداية كل برنامج التعليمات التالية:

import java.lang.*;

ماذا تعني هذه التعليمات؟ إنها تتيح الوصول إلى جميع فئات الحزمة java.lang. سيجد المُجمِّع فيها الملف System.class الذي يُعرِّف الفئة System. لا نعرف بعد أين سيجد المُجمِّع الحزمة java.lang ولا كيف تبدو الحزمة. سنعود إلى هذا الموضوع لاحقًا. لإنشاء فئة في حزمة، نكتب:

package paquetage;
// تعريف الفئة
...

على سبيل المثال، لنقم بإنشاء فئتنا personne التي درسناها سابقًا في حزمة. سنختار istia.st كاسم للحزمة. تصبح الفئة personne:


// اسم الحزمة التي سيتم إنشاء فئة الشخص فيها
package istia.st;

// فئة شخص
public class personne{
    // الاسم، الاسم الأول، العمر
    private String prenom;
    private String nom;
    private int age;
  
    // المنشئ 1
    public personne(String P, String N, int age){
        this.prenom=P;
        this.nom=N;
        this.age=age;
    }

    // toString
    public String toString(){
        return "personne("+prenom+","+nom+","+age+")";
    }

}//الفئة

يتم ترجمة هذه الفئة ثم وضعها في دليل istia\st في الدليل الحالي. لماذا istia\st؟ لأن الحزمة تسمى istia.st.

E:\data\serge\JAVA\classes\paquetages\personne>dir
06/06/2002  16:28                  467 personne.java
06/06/2002  16:04       <DIR>          istia

E:\data\serge\JAVA\classes\paquetages\personne>dir istia
06/06/2002  16:04       <DIR>          st

E:\data\serge\JAVA\classes\paquetages\personne>dir istia\st
06/06/2002  16:28                  675 personne.class

الآن لنستخدم الفئة personne في فئة اختبار أولية:


    public class test{
        public static void main(String[] args){
            istia.st.personne p1=new istia.st.personne("Jean","Dupont",20);
            System.out.println("p1="+p1);
        }//اليد
    }//فئة الاختبار

يُلاحظ أن الفئة personne أصبحت الآن مسبوقة باسم حزمة istia.st. أين سيجد المُجمع الفئة istia.st.personne؟ يبحث المُجمِّع عن الفئات التي يحتاجها في قائمة محددة مسبقًا من الدلائل وفي شجرة تبدأ من الدليل الحالي. هنا، سيبحث عن الفئة istia.st.personne في ملف istia\st\personne.class. لهذا السبب وضعنا الملف personne.class في الدليل istia\st. لنقوم بتجميع ثم تشغيل برنامج الاختبار:

E:\data\serge\JAVA\classes\paquetages\personne>dir
06/06/2002  16:28                  467 personne.java
06/06/2002  16:06                  246 test.java
06/06/2002  16:04       <DIR>          istia
06/06/2002  16:06                  738 test.class

E:\data\serge\JAVA\classes\paquetages\personne>java test
p1=personne(Jean,Dupont,20)

لتجنب الكتابة


            istia.st.personne p1=new istia.st.personne("Jean","Dupont",20);

يمكن استيراد الفئة istia.st.personne باستخدام جملة import:


import istia.st.personne;

يمكننا عندئذ كتابة


            personne p1=new personne("Jean","Dupont",20);

وسيقوم المُجمِّع بترجمتها إلى


            istia.st.personne p1=new istia.st.personne("Jean","Dupont",20);

يصبح برنامج الاختبار كما يلي:


// مساحات الأسماء المستوردة
import istia.st.personne;

public class test2{
    public static void main(String[] args){
        personne p1=new personne("Jean","Dupont",20);
        System.out.println("p1="+p1);
    }//الرئيسي
}//فئة test2

لنقوم بتجميع هذا البرنامج الجديد وتشغيله:

E:\data\serge\JAVA\classes\paquetages\personne>javac test2.java

E:\data\serge\JAVA\classes\paquetages\personne>dir
06/06/2002  16:28                  467 personne.java
06/06/2002  16:06                  246 test.java
06/06/2002  16:04       <DIR>          istia
06/06/2002  16:06                  738 test.class
06/06/2002  16:47                  236 test2.java
06/06/2002  16:50                  740 test2.class

E:\data\serge\JAVA\classes\paquetages\personne>java test2
p1=personne(Jean,Dupont,20)

لقد وضعنا الحزمة istia.st في الدليل الحالي. هذا ليس إلزامياً. لنضعها في مجلد يسمى mesClasses في الدليل الحالي. تذكر أن فئات الحزمة istia.st موضوعة في مجلد istia\st. هيكل الدليل الحالي هو كما يلي:

E:\data\serge\JAVA\classes\paquetages\personne>dir
06/06/2002  16:28                  467 personne.java
06/06/2002  16:06                  246 test.java
06/06/2002  16:06                  738 test.class
06/06/2002  16:47                  236 test2.java
06/06/2002  16:50                  740 test2.class
06/06/2002  16:21       <DIR>          mesClasses

E:\data\serge\JAVA\classes\paquetages\personne>dir mesClasses
06/06/2002  16:22       <DIR>          istia

E:\data\serge\JAVA\classes\paquetages\personne>dir mesClasses\istia
06/06/2002  16:22       <DIR>          st

E:\data\serge\JAVA\classes\paquetages\personne>dir mesClasses\istia\st
06/06/2002  16:01                1 153 personne.class

الآن لنقوم بتجميع البرنامج مرة أخرى test2.java :

E:\data\serge\JAVA\classes\paquetages\personne>javac test2.java
test2.java:2: package istia.st does not exist
import istia.st.personne;

لم يعد المُجمِّع يجد الحزمة istia.st منذ أن تم نقلها. لاحظ أنه يبحث عنها بسبب التعليمات import. بشكل افتراضي، يبحث عنه من الدليل الحالي في مجلد يسمى istia\st الذي لم يعد موجودًا. لنفحص خيارات المُجمِّع:

E:\data\serge\JAVA\classes\paquetages\personne>javac
Usage: javac <options> <source files>
where possible options include:
  -g                        Generate all debugging info
  -g:none                   Generate no debugging info
  -g:{lines,vars,source}    Generate only some debugging info
  -O                        Optimize; may hinder debugging or enlarge class file
  -nowarn                   Generate no warnings
  -verbose                  Output messages about what the compiler is doing
  -deprecation              Output source locations where deprecated APIs are used
  -classpath <path>         Specify where to find user class files
  -sourcepath <path>        Specify where to find input source files
  -bootclasspath <path>     Override location of bootstrap class files
  -extdirs <dirs>           Override location of installed extensions
  -d <directory>            Specify where to place generated class files
  -encoding <encoding>      Specify character encoding used by source files
  -source <release>         Provide source compatibility with specified release
  -target <release>         Generate class files for specific VM version
  -help                     Print a synopsis of standard options

هنا قد تكون الخيار -classpath مفيدًا لنا. فهو يسمح بإرشاد المُجمِّع إلى مكان البحث عن فئاته وحزمه. لنجرب ذلك. لنقوم بالتجميع بإخبار المُجمِّع أن الحزمة istia.st موجودة الآن في المجلد mesClasses:

E:\data\serge\JAVA\classes\paquetages\personne>javac -classpath mesClasses test2.java

E:\data\serge\JAVA\classes\paquetages\personne>dir
06/06/2002  16:47                  236 test2.java
06/06/2002  17:03                  740 test2.class
06/06/2002  16:21       <DIR>          mesClasses

تمت عملية التجميع هذه المرة دون مشاكل. لنقم بتشغيل البرنامج test2.class:

E:\data\serge\JAVA\classes\paquetages\personne>java test2
Exception in thread "main" java.lang.NoClassDefFoundError: istia/st/personne
        at test2.main(test2.java:6)

الآن جاء دور الآلة الافتراضية Java التي لا تجد الفئة istia/st/personne. فهي تبحث عنها في الدليل الحالي بينما هي موجودة الآن في الدليل mesClasses. لنلقِ نظرة على خيارات الآلة الافتراضية Java:

E:\data\serge\JAVA\classes\paquetages\personne>java
Usage: java [-options] class [args...]
           (to execute a class)
   or  java -jar [-options] jarfile [args...]
           (to execute a jar file)

where options include:
    -client       to select the "client" VM
    -server       to select the "server" VM
    -hotspot      is a synonym for the "client" VM  [deprecated]
                  The default VM is client.

    -cp -classpath <directories and zip/jar files separated by ;>
                  set search path for application classes and resources
    -D<name>=<value>
                  set a system property
    -verbose[:class|gc|jni]
                  enable verbose output
    -version      print product version and exit
    -showversion  print product version and continue
    -? -help      print this help message
    -X            print help on non-standard options
    -ea[:<packagename>...|:<classname>]
    -enableassertions[:<packagename>...|:<classname>]
                  enable assertions
    -da[:<packagename>...|:<classname>]
    -disableassertions[:<packagename>...|:<classname>]
                  disable assertions
    -esa | -enablesystemassertions
                  enable system assertions
    -dsa | -disablesystemassertions
                  disable system assertions

نرى أن JVM لديه أيضًا خيار classpath مثل المُجمع. لنستخدمه لنخبره بمكان وجود الحزمة istia.st:

E:\data\serge\JAVA\classes\paquetages\personne>java.bat -classpath mesClasses test2
Exception in thread "main" java.lang.NoClassDefFoundError: test2

لم نحرز تقدماً كبيراً. الآن لم يتم العثور على الفئة test2 نفسها. والسبب هو: في حالة عدم وجود الكلمة الرئيسية classpath، يتم استكشاف الدليل الحالي بشكل منهجي أثناء البحث عن الفئات، ولكن لا يتم ذلك عندما تكون موجودة. وبالتالي، لا يتم العثور على الفئة test2.class الموجودة في الدليل الحالي. الحل؟ إضافة الدليل الحالي إلى classpath. يتم تمثيل الدليل الحالي بالرمز .

E:\data\serge\JAVA\classes\paquetages\personne>java -classpath mesClasses;. test2
p1=personne(Jean,Dupont,20)

لماذا كل هذه التعقيدات؟ الهدف من الحزم هو تجنب تعارض الأسماء بين الفئات. لنفترض أن هناك شركتين E1 و E2 توزعان فئات مجمعة على التوالي في الحزم com.e1 و com.e2. لنفترض أن هناك عميلاً C يشتري هاتين المجموعتين من الفئات التي حددت فيهما الشركتان فئة personne. سيشير العميل C إلى الفئة personne التابعةشركة E1 بـ com.e1.personne وتلك الخاصة بشركة E2 بـ com.e2.personne، وبذلك يتجنب تعارض الأسماء.

3.6.2. البحث عن الحزم

عندما نكتب في برنامج

import java.util.*;

للوصول إلى جميع فئات الحزمة java.util، أين يتم العثور عليها؟ لقد ذكرنا أن الحزم يتم البحث عنها افتراضيًا في الدليل الحالي أو في قائمة الدلائل المعلنة في خيار classpath للمترجم أو في JVM إذا كان هذا الخيار موجودًا. كما يتم البحث عنها في الدلائل lib في دليل تثبيت JDK. لننظر إلى هذا الدليل:

Image

في هذا المثال، سيتم فحص الشجرتين jdk14\lib و jdk14\jre\lib للبحث عن ملفات .class أو ملفات .jar أو .zip التي تمثل أرشيفات للفئات. لنقم على سبيل المثال بالبحث عن ملفات .jar الموجودة ضمن الدليل jdk14 السابق:

Image

هناك العشرات منها. يمكن فتح ملف .jar باستخدام الأداة المساعدة winzip. لنفتح الملف rt.jar أعلاه (rt=RunTime). يوجد فيه عدة مئات من ملفات .class، بما في ذلك تلك التي تنتمي إلى الحزمة java.util:

Image

إحدى الطرق البسيطة لإدارة الحزم هي وضعها في الدليل <jdk>\jre\lib حيث <jdk> هو دليل تثبيت JDK. بشكل عام، تحتوي الحزمة على عدة فئات، ومن المفضل تجميعها في ملف .jar واحد (JAR=ملف Java ARchive). يوجد الملف القابل للتنفيذ jar.exe في المجلد <jdk>\bin:

E:\data\serge\JAVA\classes\paquetages\personne>dir "e:\program files\jdk14\bin\jar.exe"
07/02/2002  12:52               28 752 jar.exe

يمكن الحصول على مساعدة حول استخدام البرنامج jar عن طريق تشغيله بدون معلمات:

E:\data\serge\JAVA\classes\paquetages\personne>"e:\program files\jdk14\bin\jar.exe"
Syntaxe : jar {ctxu}[vfm0M] [fichier-jar] [fichier-manifest] [rÚp -C] fichiers ...
Options :
    -c  crÚer un nouveau fichier d''archives
    -t  gÚnÚrer la table des matiÞres du fichier d''archives
    -x  extraire les fichiers nommÚs (ou tous les fichiers) du fichier d''archives
    -u  mettre Ó jour le fichier d''archives existant
    -v  gÚnÚrer des informations verbeuses sur la sortie standard
    -f  spÚcifier le nom du fichier d''archives
    -m  inclure les informations manifest provenant du fichier manifest spÚcifiÚ
    -0  stocker seulement ; ne pas utiliser la compression ZIP
    -M  ne pas crÚer de fichier manifest pour les entrÚes
    -i  gÚnÚrer l''index pour les fichiers jar spÚcifiÚs
    -C  passer au rÚpertoire spÚcifiÚ et inclure le fichier suivant
Si un rÚpertoire est spÚcifiÚ, il est traitÚ rÚcursivement.
Les noms des fichiers manifest et d''archives doivent Ûtre spÚcifiÚs
dans l''ordre des indicateurs ''m'' et ''f''.

Exemple 1 : pour archiver deux fichiers de classe dans le fichier d''archives classes.jar :
       jar cvf classes.jar Foo.class Bar.class
Exemple 2 : utilisez le fichier manifest existant ''monmanifest'' pour archiver tous les fichiers du

           rÚpertoire foo/ dans ''classes.jar'':
       jar cvfm classes.jar monmanifest -C foo/ .

لنعد إلى الفئة personne.class التي تم إنشاؤها مسبقًا في حزمة istia.st:

E:\data\serge\JAVA\classes\paquetages\personne>dir
06/06/2002  16:28                  467 personne.java
06/06/2002  17:36                  195 test.java
06/06/2002  16:04       <DIR>          istia
06/06/2002  16:06                  738 test.class
06/06/2002  16:47                  236 test2.java
06/06/2002  18:15                  740 test2.class

E:\data\serge\JAVA\classes\paquetages\personne>dir istia
06/06/2002  16:04       <DIR>          st

E:\data\serge\JAVA\classes\paquetages\personne>dir istia\st
06/06/2002  16:28                  675 personne.class

لنقم بإنشاء ملف istia.st.jar يحفظ جميع فئات الحزمة istia.st، أي جميع فئات الشجرة istia\st أعلاه:

E:\data\serge\JAVA\classes\paquetages\personne>"e:\program files\jdk14\bin\jar" cvf istia.st.jar istia\st\*

E:\data\serge\JAVA\classes\paquetages\personne>dir
06/06/2002  16:28                  467 personne.java
06/06/2002  17:36                  195 test.java
06/06/2002  16:04       <DIR>          istia
06/06/2002  16:06                  738 test.class
06/06/2002  16:47                  236 test2.java
06/06/2002  18:15                  740 test2.class
06/06/2002  18:08                  874 istia.st.jar

دعونا نفحص مع winzip محتوى الملف istia.st.jar:

Image

لنضع الملف istia.st.jar في المجلد <jdk>\jre\lib\perso:

E:\data\serge\JAVA\classes\paquetages\personne>dir "e:\program files\jdk14\jre\lib\perso"
06/06/2002  18:08                  874 istia.st.jar

الآن نقوم بتجميع البرنامج test2.java ثم نقوم بتشغيله:

E:\data\serge\JAVA\classes\paquetages\personne>javac -classpath istia.st.jar test2.java

E:\data\serge\JAVA\classes\paquetages\personne>java -classpath istia.st.jar;. test2
p1=personne(Jean,Dupont,20)

نلاحظ أنه لم يتطلب الأمر سوى ذكر اسم الأرشيف المراد استكشافه دون الحاجة إلى تحديد موقعه بشكل صريح. يتم استكشاف جميع الدلائل في الشجرة <jdk>\jre\lib للعثور على الملف المطلوب .jar.

3.7. المثال IMPÔTS

نستأنف حساب الضريبة الذي تمت دراسته في الفصل السابق ونعالجه باستخدام فئة. لنتذكر المشكلة:

نضع أنفسنا في الحالة المبسطة لمكلف لا يملك سوى راتبه الوحيد ليصرح عنه:

  • نحسب عدد حصص الموظف nbParts=nbEnfants/2 +1 إذا كان غير متزوج، nbEnfants/2+2 إذا كان متزوجًا، حيث nbEnfants هو عدد أطفاله.
  • إذا كان لديه ثلاثة أطفال على الأقل، يحصل على نصف حصة إضافية
  • يتم حساب دخله الخاضع للضريبة R=0.72*S حيث S هو راتبه السنوي
  • نحسب معامل الأسرة QF=R/nbParts
  • نحسب ضريبته I. لننظر إلى الجدول التالي:
12620.0
0
0
13190
0.05
631
15640
0.1
1290.5
24740
0.15
2072.5
31810
0.2
3309.5
39970
0.25
4900
48360
0.3
6898.5
55790
0.35
9316.5
92970
0.4
12106
127860
0.45
16754.5
151250
0.50
23147.5
172040
0.55
30710
195000
0.60
39312
0
0.65
49062

يحتوي كل سطر على 3 حقول. لحساب الضريبة I، نبحث عن السطر الأول الذي يكون فيه QF<=الحقل 1. على سبيل المثال، إذا كان QF=23000، فسنجد السطر

    24740        0.15        2072.5

يكون الضريبة I عندئذٍ مساوية لـ 0.15*R - 2072.5*nbParts. إذا كان QF بحيث لا يتم التحقق من العلاقة QF<=champ1 أبدًا، فسيتم استخدام معاملات السطر الأخير. هنا:

    0                0.65        49062

مما يعطي الضريبة I=0.65*R - 49062*nbParts.

سيتم تعريف فئة impots على النحو التالي:


// إنشاء فئة الضرائب

public class impots{

    // البيانات اللازمة لحساب الضريبة
     // تأتي من مصدر خارجي

    private double[] limites, coeffR, coeffN;

     // الشركة المصنعة
    public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
         // نتحقق من أن الجداول الثلاثة لها نفس الحجم
        boolean OK=LIMITES.length==COEFFR.length && LIMITES.length==COEFFN.length;
        if (! OK) throw new Exception ("Les 3 tableaux fournis n'ont pas la même taille("+
                                LIMITES.length+","+COEFFR.length+","+COEFFN.length+")");
        // حسناً
        this.limites=LIMITES;
        this.coeffR=COEFFR;
        this.coeffN=COEFFN;
    }//المصنع

     // حساب الضريبة
    public long calculer(boolean marié, int nbEnfants, int salaire){
         // حساب عدد الحصص
        double nbParts;
        if (marié) nbParts=(double)nbEnfants/2+2;
        else nbParts=(double)nbEnfants/2+1;
        if (nbEnfants>=3) nbParts+=0.5;
         // حساب الدخل الخاضع للضريبة و"الناتج العائلي"
        double revenu=0.72*salaire;
        double QF=revenu/nbParts;
         // حساب الضريبة
        limites[limites.length-1]=QF+1;
        int i=0;
        while(QF>limites[i]) i++;
        // إرجاع النتيجة
        return (long)(revenu*coeffR[i]-nbParts*coeffN[i]);
    }//الحساب
}//الفئة

يتم إنشاء كائن impots بالبيانات التي تسمح بحساب ضريبة دافع الضرائب. هذا هو الجزء الثابت من الكائن. بمجرد إنشاء هذا الكائن، يمكن استدعاء طريقة calculer الخاصة به بشكل متكرر، والتي تحسب ضريبة دافع الضرائب بناءً على حالته الاجتماعية (متزوج أم لا)، وعدد أطفاله، وراتبه السنوي.

قد يكون برنامج الاختبار كما يلي:


//الفئات المستوردة
// استيراد الضرائب؛
import java.io.*;

    public class test
    {
        public static void main(String[] arg) throws IOException
        {
             // برنامج تفاعلي لحساب الضرائب
             // يكتب المستخدم ثلاث بيانات على لوحة المفاتيح: متزوج nbEnfants الراتب
             // ثم يعرض البرنامج الضريبة المستحقة

            final String syntaxe="syntaxe : marié nbEnfants salaire\n"
                            +"marié : o pour marié, n pour non marié\n"
                            +"nbEnfants : nombre d'enfants\n"
                            +"salaire : salaire annuel en F";

             // جداول البيانات اللازمة لحساب الضريبة
            double[] limites=new double[] {12620,13190,15640,24740,31810,39970,48360,55790,92970,127860,151250,172040,195000,0};
            double[] coeffR=new double[] {0,0.05,0.1,0.15,0.2,0.25,0.3,0.35,0.4,0.45,0.5,0.55,0.6,0.65};
            double[] coeffN=new double[] {0,631,1290.5,2072.5,3309.5,4900,6898.5,9316.5,12106,16754.5,23147.5,30710,39312,49062};

       // إنشاء تدفق قراءة
      BufferedReader IN=new BufferedReader(new InputStreamReader(System.in));
            // إنشاء كائن ضريبة
            impots objImpôt=null;
            try{
                objImpôt=new impots(limites,coeffR,coeffN);
            }catch (Exception ex){
                System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
                System.exit(1);
            }//try-catch

            // حلقة لا نهائية
            while(true){
                 // طلب معلمات حساب الضريبة
                System.out.print("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :");
                String paramètres=IN.readLine().trim();
                // هل هناك شيء يجب فعله؟
                if(paramètres==null || paramètres.equals("")) break;
                // التحقق من عدد الحجج في السطر الذي تم إدخاله
                String[] args=paramètres.split("\\s+");
                int nbParamètres=args.length;
                if (nbParamètres!=3){
                    System.err.println(syntaxe);
                    continue;
                }//if
                 // التحقق من صحة المعلمات
                // متزوج
                String marié=args[0].toLowerCase();
                if (! marié.equals("o") && ! marié.equals("n")){
                    System.err.println(syntaxe+"\nArgument marié incorrect : tapez o ou n");
                    continue;
                }//if
                 // nbEnfants
                int nbEnfants=0;
                try{
                    nbEnfants=Integer.parseInt(args[1]);
                    if(nbEnfants<0) throw new Exception();
                }catch (Exception ex){
                    System.err.println(syntaxe+"\nArgument nbEnfants incorrect : tapez un entier positif ou nul");
                    continue;
                }//if
                 // الراتب
                int salaire=0;
                try{
                    salaire=Integer.parseInt(args[2]);
                    if(salaire<0) throw new Exception();
                }catch (Exception ex){
                    System.err.println(syntaxe+"\nArgument salaire incorrect : tapez un entier positif ou nul");
                    continue;
                }//if
                 // المعلمات صحيحة - يتم حساب الضريبة
                System.out.println("impôt="+objImpôt.calculer(marié.equals("o"),nbEnfants,salaire)+" F");
                 // المكلف التالي
            }//while
        }//الرئيسي
    }//فئة

فيما يلي مثال على تنفيذ البرنامج السابق:

E:\data\serge\MSNET\c#\impots\3>اختبار جافا

Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :q s d
syntaxe : marié nbEnfants salaire
marié : o pour marié, n pour non marié
nbEnfants : nombre d'enfants
salaire : salaire annuel en F
Argument marié incorrect : tapez o ou n

Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o s d
syntaxe : marié nbEnfants salaire
marié : o pour marié, n pour non marié
nbEnfants : nombre d'enfants
salaire : salaire annuel en F
Argument nbEnfants incorrect : tapez un entier positif ou nul

Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 d
syntaxe : marié nbEnfants salaire
marié : o pour marié, n pour non marié
nbEnfants : nombre d'enfants
salaire : salaire annuel en F
Argument salaire incorrect : tapez un entier positif ou nul

Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :q s d f
syntaxe : marié nbEnfants salaire
marié : o pour marié, n pour non marié
nbEnfants : nombre d'enfants
salaire : salaire annuel en F

Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 200000
impôt=22504 F

Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :