Skip to content

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

3.1. الموضوع مع أمثلة توضيحية

3.1.1. نظرة عامة

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

public class C1{
    type1 p1;            // property p1
    type2 p2;            // property p2
    
    type3 m3(){        // m3 method
        
    }
    type4 m4(){        // m4 method
        
    }
    
}

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

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

لننظر إلى نموذج الكائن الأول: فئة Person.

3.1.2. تعريف فئة Person

تعريف فئة Person هو كما يلي:


import java.io.*;
 
public class personne{
  // attributes
  private String prenom;
  private String nom;
  private int age;
  
  // method
  public void initialise(String P, String N, int age){
    this.prenom=P;
    this.nom=N;
    this.age=age;
  }
 
  // method
  public void identifie(){
    System.out.println(prenom+","+nom+","+age);
  }
}

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

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

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

عام: يمكن الوصول إلى الحقل العام من خلال أي دالة، سواء تم تعريفه داخل الفئة أم لا

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

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

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

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

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


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
}

ملاحظات

  • ترتيب إعلان السمات الخاصة والمحمية والعامة هو ترتيب تعسفي.

3.1.3. طريقة initialize

لنعد إلى فئة Person التي تم تعريفها على النحو التالي:


import java.io.*;
 
public class personne{
  // attributes
  private String prenom;
  private String nom;
  private int age;
  
  // method
  public void initialise(String P, String N, int age){
    this.prenom=P;
    this.nom=N;
    this.age=age;
  }
 
  // method
  public void identifie(){
    System.out.println(prenom+","+nom+","+age);
  }
}

ما هو دور طريقة initialize؟ نظرًا لأن lastName و firstName و age هي عناصر خاصة في فئة Person، فإن العبارات

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

غير صالحة. يجب علينا تهيئة كائن من نوع Person باستخدام طريقة عامة. وهذا هو دور طريقة initialize. نكتب:

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

صيغة p1.initialize صحيحة لأن initialize عامة.

3.1.4. المشغل new

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

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

غير صحيح. العبارة

    personne p1;

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

personne p1=null;

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

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

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

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

personne p1=new personne();

يؤدي هذا إلى إنشاء كائن Person غير مُهيأ: ستكون قيمة السمتين name و first_name، اللتين تشيران إلى كائنات String، هي null، وستكون قيمة age هي 0. وبالتالي، هناك تهيئة افتراضية. والآن بعد أن أصبح p1 يشير إلى كائن، فإن عبارة التهيئة لهذا الكائن

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

صالحًا.

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

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


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

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

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

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


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();
  }
}

يتم تعريف فئة Person في ملف المصدر person.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 لا يستورد فئة person باستخدام عبارة:

import personne;

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

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

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

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

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

// classes importées
// import personne;

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

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

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

// imported packages
import java.io.*;

class personne{
   // attributes
  private String prenom;    // first name
  private String nom;            // its name
  private int age;                // his age

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

   // method
  public void identifie(){
    System.out.println(prenom+","+nom+","+age);
  }//identifies
}//class
public class test2{
  public static void main(String arg[]){
    personne p1=new personne();
    p1.initialise("Jean","Dupont",30);
    p1.identifie();
  }
}

لاحظ أن فئة Person لم تعد تحتوي على السمة public. في الواقع، في ملف مصدر Java، لا يمكن أن تحتوي سوى فئة واحدة على السمة 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. طريقة أخرى تقوم بالتهيئة

لنواصل مع فئة Person ونضيف إليها الطريقة التالية:


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

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

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


// import nobody;
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 من الحجج args، فيمكن إجراء إعلان وتهيئة كائن من تلك الفئة على النحو التالي:


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

أو


        classe objet;

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

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

    personne p1;
    p1=new personne();

لنقم بإنشاء منشئين لفئة Person الخاصة بنا:


public class personne{
  // attributes
  private String prenom;
  private String nom;
  private int age;
  
  // manufacturers
  public personne(String P, String N, int age){
    initialise(P,N,age);
  }
  public personne(personne P){
    initialise(P);
  }
 
  // method
  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;
  }
 
  // method
  public void identifie(){
    System.out.println(prenom+","+nom+","+age);
  }
}

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

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


// import nobody;
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. مراجع الكائنات

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


// import nobody;
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 references the same object as p1
    personne p2=p1;
    System.out.print("p2="); p2.identifie();
    // p3 references an object that will be a copy of the object referenced by p1
    personne p3=new personne(p1);
    System.out.print("p3=");  p3.identifie();
    // change the state of the object referenced by p1
    p1.initialise("Micheline","Benoît",67);
    System.out.print("p1=");  p1.identifie();
    // as p2=p1, the object referenced by p2 must have changed state
    System.out.print("p2=");  p2.identifie();
    // as p3 does not reference the same object as p1, the object referenced by p3 must not have changed
    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 إلى الكائن person("Jean","Dupont",30) ولكنها ليست الكائن نفسه. في لغة C، يمكننا القول إنها مؤشر، أي عنوان الكائن الذي تم إنشاؤه. إذا كتبنا بعد ذلك:

    p1=null

فليس الكائن person("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. الكائنات المؤقتة

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

انظر المثال التالي:


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

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


// manufacturers
  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. طرق قراءة وكتابة السمات الخاصة

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


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);
  }
 
  // accessors
  public String getPrenom(){
    return prenom;
  }
  public String getNom(){
    return nom;
  }
  public int getAge(){
    return age;
  }
 
  //modifiers
  public void setPrenom(String P){
    this.prenom=P;
  }
  public void setNom(String N){
    this.nom=N;
  }
  public void setAge(int age){
    this.age=age;
  }
}

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


// import nobody;
 
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. طرق وسمات الفئة

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

    private static long nbPersonnes;        // number of people created

للإشارة إليها، نكتب person.nbPeople لإظهار أنها سمة تابعة لفئة Person نفسها. هنا، أنشأنا سمة خاصة لا يمكن الوصول إليها مباشرة من خارج الفئة. لذلك، نقوم بإنشاء طريقة عامة لتوفير الوصول إلى سمة الفئة nbPersonnes. لإرجاع قيمة nbPersonnes، لا تحتاج الطريقة إلى كائن محدد: في الواقع، nbPersonnes ليست سمة لكائن محدد؛ إنها سمة للفئة بأكملها. لذلك، نحتاج إلى طريقة فئة يتم إعلانها أيضًا على أنها ثابتة:

public static long getNbPersonnes(){
    return nbPersonnes;
}

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

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


public class personne{
  
  // class attribute
  private static long nbPersonnes=0;
 
  // object attributes

 
  // manufacturers
  public personne(String P, String N, int age){
    initialise(P,N,age);
    nbPersonnes++;
  }
  public personne(personne P){
    initialise(P);
    nbPersonnes++;
  }
 
  // method

 
  // class method
  public static long getNbPersonnes(){
    return nbPersonnes;
  }
 
}// class

مع البرنامج التالي:


// import nobody;
 
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());
  }// hand
}//test1

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

    Nombre de personnes créées : 3

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

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

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

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

Image

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


// import nobody;
 
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();
  }// hand
 
  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();
  }// modify
}// class

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

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 في الدالة الرئيسية، وأن الكائن قد تم تعديله بالفعل بواسطة الدالة modify.

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;
  }
}

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


// import integer;
 
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. مجموعة من الأشخاص

الكائن هو جزء من البيانات مثل أي جزء آخر، وبالتالي، يمكن تجميع عدة كائنات معًا في مصفوفة:


// import nobody;
 
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();
  }
}

يُنشئ البيان person[] friends = new person[3]; مصفوفة من 3 عناصر من النوع person. يتم تهيئة هذه العناصر الثلاثة هنا بالقيمة null، مما يعني أنها لا تشير إلى أي كائنات. مرة أخرى، من الناحية الفنية، نشير إلى "مصفوفة من الكائنات" عندما تكون في الواقع مجرد مصفوفة من مراجع الكائنات. إن إنشاء مصفوفة الكائنات — وهي مصفوفة تعتبر كائنًا بحد ذاتها (كما يشير إلى ذلك استخدام 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. نظرة عامة

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

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


    public class enseignant extends personne

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


class enseignant extends personne{
// attributes
  private int section;
 
// manufacturer
  public enseignant(String P, String N, int age,int section){
    super(P,N,age);
    this.section=section;
  }
}

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


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+")";
  }
 
  // accessors
  public String getPrenom(){
    return prenom;
  }
  public String getNom(){
    return nom;
  }
  public int getAge(){
    return age;
  }
 
  //modifiers
  public void setPrenom(String P){
    this.prenom=P;
  }
  public void setNom(String N){
    this.nom=N;
  }
  public void setAge(int age){
    this.age=age;
  }
}

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

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

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

فيما يلي منشئ فئة Teacher:


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

العبارة super(P,N,age) هي استدعاء لمُنشئ الفئة الأصلية، وهي في هذه الحالة فئة Person. ونحن نعلم أن هذا المُنشئ يقوم بتهيئة الحقول first_name و last_name و age الخاصة بكائن Person الموجود داخل كائن Student. يبدو هذا معقدًا بعض الشيء، وقد نفضل كتابة:


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

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

دعونا نجرب أول برنامج:


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

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

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

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. تحميل الطرق

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


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+")";    
  }    
}

تعتمد طريقة identity الخاصة بفئة Teacher على طريقة identity الخاصة بفئتها الأم (super.identity) لعرض جزء "person" الخاص بها، ثم تضيف حقل section، وهو خاص بفئة Teacher.

أصبحت فئة Teacher الآن تحتوي على طريقتين للهوية:

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

إذا كان E كائنًا من فئة Teacher، فإن E.identity تشير إلى طريقة الهوية لفئة Teacher. نقول إن طريقة الهوية للفئة الأصلية "تم تجاوزها" بواسطة طريقة الهوية للفئة الفرعية. بشكل عام، إذا كان O كائنًا و M طريقة، لتنفيذ الطريقة O.M، يبحث النظام عن الطريقة M بالترتيب التالي:

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

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

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


// import nobody;
// import teacher;
 
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 في دالة العرض قيمة من النوع Teacher. ونظرًا لأن نوع Teacher مشتق من Object، فإن هذا الأمر صحيح.

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

دعونا نكمل دالة العرض الخاصة بنا:

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

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

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

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

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

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


// import nobody;
// import teacher;
 
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

أي class_name@object_address. ونظرًا لأن هذا ليس واضحًا تمامًا، فقد نميل إلى تعريف طريقة toString لفئتي Person و Student والتي ستتجاوز طريقة toString الخاصة بالفئة الأصلية Object. وبدلاً من كتابة طرق مشابهة لطرق الهوية الموجودة بالفعل في فئتي Person و Teacher، دعونا ببساطة نعيد تسمية طرق الهوية تلك إلى 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. الفئات الداخلية

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

// imported classes
import java.io.*;

public class test1{

    // internal class
    private class article{
         // we define the structure
        private String code;
        private String nom;
        private double prix;
        private int stockActuel;
        private int stockMinimum;

     // manufacturer
    public article(String code, String nom, double prix, int stockActuel, int stockMinimum){
         // attribute initialization
      this.code=code;
      this.nom=nom;
      this.prix=prix;
      this.stockActuel=stockActuel;
      this.stockMinimum=stockMinimum;
    }//manufacturer

    //toString
    public String toString(){
        return "article("+code+","+nom+","+prix+","+stockActuel+","+stockMinimum+")";
    }//toString
  }//item class

   // local data
  private article art=null;

   // manufacturer
  public test1(String code, String nom, double prix, int stockActuel, int stockMinimum){
       // attribute definition
    art=new article(code, nom, prix, stockActuel,stockMinimum);
  }//test1

   // accessor
  public article getArticle(){
      return art;
  }//getArticle

    public static void main(String arg[]){
      // create a test1 instance
      test1 t1=new test1("a100","velo",1000,10,5);
    // display test1.art
    System.out.println("art="+t1.getArticle());
  }//hand

}// fin class

تحتوي فئة 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.

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


     // a student class
public class élève{
     // public attributes
    public String nom;
    public double note;
     // manufacturer
    public élève(String NOM, double NOTE){
        nom=NOM;
        note=NOTE;
    }//manufacturer
}//student  

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


// imported classes
// student import
 
// class notes
public class notes{
 
     // attributes
    protected String matière;
    protected élève[] élèves;
 
     // manufacturer
    public notes (String MATIERE, élève[] ELEVES){
         // student & subject memorization
        matière=MATIERE;
        élèves=ELEVES;
    }//notes
 
    // toString
    public String toString(){
        String valeur="matière="+matière +", notes=(";
        int i;
         // concatenate all the notes
        for (i=0;i<élèves.length-1;i++){
            valeur+="["+élèves[i].nom+","+élèves[i].note+"],";
        };
         //final note
        if(élèves.length!=0){ valeur+="["+élèves[i].nom+","+élèves[i].note+"]";}
        valeur+=")";
         // end
        return valeur;
    }//toString
}//class

تم تعريف سمات subject و students على أنها محمية (protected) بحيث يمكن الوصول إليها من فئة مشتقة. قررنا اشتقاق فئة notes إلى فئة notesStats التي ستحتوي على سمتين إضافيتين: المتوسط والانحراف المعياري للدرجات:


public class notesStats extends notes implements Istats {
     // attributes
    private double _moyenne;
    private double _écartType;

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


// an interface
public interface Istats{
    double moyenne();
    double écartType();
}//

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


// imported classes
// import notes;
// import Istats;
// student import;
 
public class notesStats extends notes implements Istats {
     // attributes
    private double _moyenne;
    private double _écartType;
 
     // manufacturer
    public notesStats (String MATIERE, élève[] ELEVES){
         // parent class construction
        super(MATIERE,ELEVES);
         // average score calculation
        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;
         // standard deviation
        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;
    }//manufacturer
 
 
     // ToString
    public String toString(){
        return super.toString()+",moyenne="+_moyenne+",écart-type="+_écartType;
    }//ToString
 
     // istats interface methods
    public double moyenne(){
         // makes the average score
        return _moyenne;
    }//average
    public double écartType(){
         // makes the standard deviation
        return _écartType;
    }//écartType
}//class

يتم حساب المتوسط _mean والانحراف المعياري _standardDev عند إنشاء الكائن. لذلك، فإن طريقتي mean و standardDev تعيدان ببساطة قيم السمتين _mean و _standardDev. تعيد كلتا الطريقتين -1 إذا كان مصفوفة الطلاب فارغًا.

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


// imported classes
// student import;
// import Istats;
// import notes;
// import notesStats;
 
// test class
public class test{
    public static void main(String[] args){
        // some students & notes
        élève[] ELEVES=new élève[] { new élève("paul",14),new élève("nicole",16), new élève("jacques",18)};
         // recorded in a notes object
        notes anglais=new notes("anglais",ELEVES);
         // and display
        System.out.println(""+anglais);
         // idem with mean and standard deviation
        anglais=new notesStats("anglais",ELEVES);
        System.out.println(""+anglais);
    }//hand
}//class
 

تُرجع النتائج:


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 أن تنفذ طريقتي average و standardDev بنفسها دون الإشارة إلى أنها تنفذ واجهة Istats. فما الفائدة من الواجهات إذن؟ الفائدة هي التالية: يمكن لأي دالة أن تقبل كمعلمة قيمة من النوع I. وبالتالي، يمكن لأي كائن من فئة C ينفذ واجهة I أن يكون معلمة لتلك الدالة. لننظر إلى الواجهة التالية:


// an Iexample interface
public interface Iexemple{
    int ajouter(int i,int j);
    int soustraire(int i,int j);
}//interface

تحدد واجهة Iexemple طريقتين: add و subtract. وتقوم الفئتان التاليتان، class1 و class2، بتنفيذ هذه الواجهة.


// imported classes
// import 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;
    }
}//class

// imported classes
// import Iexample;
 
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;
    }
}//class

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


// imported classes
// import class1;
// import class2;
 
// test class
public class test{
    // a static function
    private static void calculer(int i, int j, Iexemple inter){
        System.out.println(inter.ajouter(i,j));
        System.out.println(inter.soustraire(i,j));
    }//calculate
 
     // the main function
    public static void main(String[] arg){
        // creation of two objects class1 and class2
        classe1 c1=new classe1();
        classe2 c2=new classe2();
        // static function calls calculate
        calculer(4,3,c1);
        calculer(14,13,c2);
    }//hand
}//test class

تقبل الدالة الثابتة calculate عنصرًا من النوع Iexample كمعلمة. وبالتالي، يمكنها استلام كائن من النوع class1 أو من النوع class2 لهذه المعلمة. وهذا ما يتم في الدالة الرئيسية مع النتائج التالية:

17
21
127
201

يمكننا أن نرى، بالتالي، أن هذه الخاصية تشبه تعدد الأشكال الذي نراه في الفئات. وبالتالي، إذا كانت مجموعة من الفئات Ci غير مرتبطة ببعضها البعض من خلال التوريث (وبالتالي لا يمكنها استخدام تعدد الأشكال القائم على التوريث) تحتوي على مجموعة من الطرق ذات التوقيع نفسه، فقد يكون من المفيد تجميع هذه الطرق في واجهة I ترث منها جميع الفئات ذات الصلة. يمكن بعد ذلك استخدام مثيلات هذه الفئات Ci كمعلمات للوظائف التي تقبل معلمة من النوع I، أي الوظائف التي تستخدم فقط أساليب كائنات 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

أخيرًا، لاحظ أن وراثة الواجهة يمكن أن تكون متعددة، أي يمكن للمرء أن يكتب

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

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

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

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


// imported classes
// import Iexample;
 
// test class
public class test2{
 
  // an internal class
  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;
        }
    };//definition class3
 
     // a static function
    private static void calculer(int i, int j, Iexemple inter){
        System.out.println(inter.ajouter(i,j));
        System.out.println(inter.soustraire(i,j));
    }//calculate
 
     // the main function
    public static void main(String[] arg){
        // creation of two objects implementing the Iexemple interface
        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;
        }
    };//definition 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;
        }
    };//definition i2
        // another object Iexample
    Iexemple i3=new classe3();
 
        // static function calls calculate
        calculer(4,3,i1);
        calculer(14,13,i2);
    calculer(24,23,i3);
    }//hand
}//test class
 

الميزة الرئيسية موجودة في الكود:


         // creation of two objects implementing the Iexemple interface
        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;
        }
    };//definition i1

نقوم بإنشاء كائن i1 الذي يهدف فقط إلى تنفيذ واجهة Iexample. هذا الكائن من نوع Iexample. وبالتالي، يمكننا إنشاء كائنات من نوع الواجهة. تعيد العديد من أساليب فئات Java كائنات من نوع الواجهة، أي كائنات يهدف وجودها فقط إلى تنفيذ أساليب واجهة ما. لإنشاء الكائن i1، قد يميل المرء إلى كتابة:


        Iexemple i1=new Iexemple()

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


        Iexemple i1=new Iexemple(){
        public int ajouter(int a, int b){
            // définition de ajouter
        }
        public int soustraire(int a, int b){
            // définition de soustraire
        }
    };//définition i1

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

public class test2{
................
// an internal class
private static class classe1 implements Iexemple{
        public int ajouter(int a, int b){
            // definition of add
        }
        public int soustraire(int a, int b){
            // definition of subtract
        }
};//definition class1
.................
    public static void main(String[] arg){
...........
        Iexemple i1=new classe1();
}//hand
}//class

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

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

17
21
127
201
1047
2001

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

// imported classes
// import 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;
    }
};//definition class3

public class test4{

     // a static function
    private static void calculer(int i, int j, Iexemple inter){
        System.out.println(inter.ajouter(i,j));
        System.out.println(inter.soustraire(i,j));
    }//calculate

   // hand method
    public static void main(String args[]){
       // definition of an anonymized class deriving from class3
     // to redefine subtract
    classe3 i1=new classe3(){
        public int ajouter(int a, int b){
          return a+b+10000;
      }//subtract
    };//i1
          // static function calls calculate
        calculer(4,3,i1);
  }//hand
}//class 

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

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

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

3.6. P ackages

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");
    }//hand
}//class

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

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 حزمة java.lang تلقائيًا. لذا، فإن الأمر يشبه وجود العبارة التالية في بداية كل برنامج:

import java.lang.*;

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

package paquetage;
// class definition
...

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


// name of the package in which the person class will be created
package istia.st;
 
// class person
public class personne{
    // last name, first name, age
    private String prenom;
    private String nom;
    private int age;
  
    // builder 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+")";
    }
 
}//class
 

يتم ترجمة هذه الفئة ثم وضعها في الدليل 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

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


    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);
        }//hand
    }//test class

لاحظ أن فئة Person أصبحت الآن مسبوقة باسم الحزمة الخاصة بها، istia.st. أين سيجد المُجمِّع فئة istia.st.Person؟ يبحث المُترجم عن الفئات التي يحتاجها في قائمة محددة مسبقًا من الدلائل وفي شجرة الدلائل بدءًا من الدليل الحالي. هنا، سيبحث عن الفئة 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 istia.st.personne;

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


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

وسيقوم المُترجم بترجمة هذا إلى


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

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


// imported namespaces
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);
    }//hand
}//test2 class

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

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 لأننا قمنا بنقلها. لاحظ أنه يبحث عنها بسبب عبارة الاستيراد. بشكل افتراضي، يبحث عنها في الدليل الحالي في مجلد يسمى 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 لديها أيضًا خيار مسار الفئة، تمامًا مثل المُجمِّع. دعونا نستخدمه لإخبار JVM بمكان وجود حزمة 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 الموجود في الدليل الحالي. الحل؟ أضف الدليل الحالي إلى مسار الفئات. يتم تمثيل الدليل الحالي بالرمز .

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

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

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'existing archives
    -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 for jar files 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 must be spÚcifiÚs
dans l'order of ''m'' and ''f'' indicators.

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 'mymanifest'' to archive all files in the

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

لنعد إلى فئة *person.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

دعونا نفحص محتويات ملف istia.st.jar باستخدام WinZip:

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. مثال حساب الضريبة

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

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

  • نحسب عدد شرائح الضريبة للموظف nbParts = nbEnfants/2 + 1 إذا كان غير متزوج، و nbEnfants/2 + 2 إذا كان متزوجًا، حيث nbEnfants هو عدد الأطفال.
  • إذا كان لديه ثلاثة أطفال على الأقل، يحصل على نصف حصة إضافية
  • نحسب دخله الخاضع للضريبة R = 0.72 * S، حيث S هو راتبه السنوي
  • نحسب معامل الأسرة QF = R / nbParts
  • نحسب ضريبته. انظر الجدول التالي:
12620.0
0
0
13,190
0.05
631
15,640
0.1
1,290.5
24,740
0.15
2,072.5
31,810
0.2
3,309.5
39,970
0.25
4,900
48,360
0.3
6,898.5
55,790
0.35
9,316.5
92,970
0.4
12,106
127,860
0.45
16,754.5
151,250
0.50
23,147.5
172,040
0.55
30,710
195,000
0.60
39,312
0
0.65
49,062

يحتوي كل صف على 3 حقول. لحساب الضريبة I، ابحث عن أول صف حيث QF <= الحقل 1. على سبيل المثال، إذا كان QF = 23,000، فسيكون الصف الذي تم العثور عليه هو

    24740        0.15        2072.5

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

    0                0.65        49062

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

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


// creation of an impots class
 
public class impots{
 
    // data required for tax calculation
     // come from an external source
 
    private double[] limites, coeffR, coeffN;
 
     // manufacturer
    public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
         // check that the 3 arrays have the same size
        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+")");
        // it's good
        this.limites=LIMITES;
        this.coeffR=COEFFR;
        this.coeffN=COEFFN;
    }//manufacturer
 
     // tAX CALCULATION
    public long calculer(boolean marié, int nbEnfants, int salaire){
         // calculating the number of shares
        double nbParts;
        if (marié) nbParts=(double)nbEnfants/2+2;
        else nbParts=(double)nbEnfants/2+1;
        if (nbEnfants>=3) nbParts+=0.5;
         // calculation of taxable income & family quota
        double revenu=0.72*salaire;
        double QF=revenu/nbParts;
         // tAX CALCULATION
        limites[limites.length-1]=QF+1;
        int i=0;
        while(QF>limites[i]) i++;
        // return result
        return (long)(revenu*coeffR[i]-nbParts*coeffN[i]);
    }//calculate
}//class

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

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


//imported classes
// import impots;
import java.io.*;
 
    public class test
    {
        public static void main(String[] arg) throws IOException
        {
             // interactive tax calculator
             // the user enters three data points on the keyboard: married nbEnfants salary
             // the program then displays the tax payable
 
            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";
 
             // data tables required for tax calculation
            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};
 
       // create a reading flow
      BufferedReader IN=new BufferedReader(new InputStreamReader(System.in));
            // tax object creation
            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
 
            // infinite loop
            while(true){
                 // tax calculation parameters are requested
                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();
                // anything to do?
                if(paramètres==null || paramètres.equals("")) break;
                // check the number of arguments in the input line
                String[] args=paramètres.split("\\s+");
                int nbParamètres=args.length;
                if (nbParamètres!=3){
                    System.err.println(syntaxe);
                    continue;
                }//if
                 // checking the validity of parameters
                // married
                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
                 // salary
                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
                 // parameters are correct - tax is calculated
                System.out.println("impôt="+objImpôt.calculer(marié.equals("o"),nbEnfants,salaire)+" F");
                 // next taxpayer
            }//while
        }//hand
    }//class

فيما يلي مثال على البرنامج السابق أثناء العمل:

E:\data\serge\MSNET\c#\impots\3>java test

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 :