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 از QZXW2HTMLP0003 اشاره دارد07ZQX.

بیایید یک مدل شیء اولیه را در نظر بگیریم: کلاس 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é: یک فیلد خصوصی فقط توسط متدهای داخلی کلاس قابل دسترسی است

public: یک فیلد عمومی برای هر تابع قابل دسترسی است، چه در داخل کلاس تعریف شده باشد چه نباشد

protégé: یک فیلد محافظت‌شده فقط توسط متدهای داخلی کلاس یا توسط یک شیء مشتق‌شده قابل دسترسی است (مفهوم ارث‌بری را بعداً ببینید).

به‌طور کلی، داده‌های یک کلاس به‌صورت خصوصی و متدهای آن به‌صورت عمومی تعریف می‌شوند. این بدان معناست که کاربر یک شیء (برنامه‌نویس)

a: دسترسی مستقیم به داده‌های خصوصی شیء نخواهد داشت

b: قادر خواهد بود متدهای عمومی شیء را فراخوانی کند، به‌ویژه آنهایی که دسترسی به داده‌های خصوصی آن را فراهم می‌کنند.

سینتکس برای اعلام یک شی به شرح زیر است:


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

  • ترتیب اعلام ویژگی‌های خصوصی، محافظت‌شده و عمومی دلخواه است.

3.1.3. متد مقداردهی اولیه می‌کند

بیایید به کلاس **person** خود بازگردیم که به صورت زیر تعریف شده است:


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 چیست؟ از آنجا که نام خانوادگی، نام و سن داده‌های خصوصی کلاس Person هستند، دستورات

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. اپراتور جدید

ترتیب دستورات

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 باید وارد شود، در آینده در ابتدای برنامه موارد زیر را خواهیم نوشت:

//کلاسهای واردشده
// import 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 person;
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 person;
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 می‌گوییم که این یک اشارهگر (pointer) به نشانی شیء ایجادشده است. اگر سپس بنویسیم:

    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. ابجکت‌های موقت

در یک عبارت، می‌توان به‌طور صریح سازنده (constructor) یک شیء را فراخوانی کرد: شیء ایجاد می‌شود، اما ما به آن دسترسی نداریم (برای مثال برای تغییر آن). این شیء موقت برای هدف ارزیابی عبارت ایجاد می‌شود و سپس دور ریخته می‌شود. فضای حافظه‌ای که اشغال کرده بود، متعاقباً توسط برنامه‌ای به نام «زباله‌جمع‌کن» (garbage collector) به طور خودکار بازیابی می‌شود، که وظیفه‌اش بازیابی فضای حافظه‌ای است که توسط اشیائی اشغال شده که دیگر توسط داده‌های برنامه به آن‌ها ارجاع داده نمی‌شود.

مثال زیر را در نظر بگیرید:


// import person;

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

}// کلاس

با استفاده از برنامه زیر:


// import person;

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

نتایج زیر به دست آمده‌اند:

    Nombre de personnes créées : 3

3.1.13. ارسال یک شیء به یک تابع

ما قبلاً اشاره کرده‌ایم که جاوا پارامترهای واقعی را به یک تابع به صورت مقدار پاس می‌کند: مقادیر پارامترهای واقعی در پارامترهای اسمی کپی می‌شوند. بنابراین یک تابع نمی‌تواند پارامترهای واقعی را تغییر دهد.

در مورد یک شیء، نباید از سوءاستفاده رایج زبانی فریب خورد، جایی که افراد به طور سیستماتیک به «شیء» اشاره می‌کنند تا «اشاره‌گر شیء». یک شیء تنها از طریق یک مرجع (اشاره‌گر) به آن دستکاری می‌شود. بنابراین آنچه به یک تابع ارسال می‌شود خود شیء نیست، بلکه مرجعی به آن شیء است. در نتیجه، این مقدار مرجع است که به پارامتر شکلی کپی می‌شود، نه مقدار خود شیء: هیچ شیء جدیدی ایجاد نمی‌شود.

اگر یک مرجع شیء R1 به یک تابع پاس شود، آن در پارامتر فرمال متناظر R2 کپی خواهد شد. در نتیجه، ارجاعات R2 و R1 به یک شیء واحد اشاره می‌کنند. اگر تابع شیء اشاره شده توسط R2 را تغییر دهد، واضح است که همان شیء اشاره شده توسط R1 را نیز تغییر می‌دهد، زیرا این دو یکی هستند.

Image

این موضوع با مثال زیر نشان داده می‌شود:


// import Person;

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

  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 اعلام شده است زیرا این یک متد کلاس است: برای فراخوانی نیازی به پیش‌وند «object» ندارد. نتایج به‌دست‌آمده به شرح زیر است:

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. دربرگیری پارامترهای خروجی یک تابع در درون یک شیء

از آنجا که پارامترها به صورت مقدار منتقل می‌شوند، نوشتن تابعی در جاوا که دارای پارامترهای خروجی از نوع 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 integers;

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

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]; یک آرایه ۳ عنصری از نوع 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. ما می‌دانیم که این سازنده فیلدهای 'first_name'، 'surname' و '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) اعلام می‌کرد (protected): در این صورت، به کلاس‌های مشتق اجازه دسترسی مستقیم به این سه فیلد داده می‌شد. در مثال ما، استفاده از سازنده کلاس والد therefore راه‌حل صحیح بود و این رویکرد استاندارد است: هنگام ساختن یک شیء فرزند، ابتدا سازنده شیء والد را فراخوانی می‌کنیم و سپس مراحل اولیه را که مختص شیء فرزند است (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
  • در کلاس والد آن، اگر وجود داشته باشد
  • در کلاس والد کلاس والد آن، اگر وجود داشته باشد
  • و غیره…

وراثت بنابراین اجازه می‌دهد متدهایی با نام یکسان با متدهای کلاس والد در کلاس فرزند بازنویسی (override) شوند. این همان چیزی است که کلاس فرزند را قادر می‌سازد تا با نیازهای خود سازگار شود. ترکیب وراثت با چندریختی (polymorphism) که کمی بعد به آن خواهیم پرداخت، بارگذاری متد (method overloading) را به اصلی‌ترین مزیت وراثت تبدیل می‌کند.

بیایید همان مثال قبلی را در نظر بگیریم:


// واردکننده شخص;
// وارد معلم;

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 مشتق شده باشد نیز اشاره کند، این قابلیت چندریختی (polymorphism) نامیده می‌شود: توانایی یک متغیر برای اشاره به انواع مختلف اشیاء.

بیایید یک مثال بزنیم و تابع زیر را در نظر بگیریم که به هیچ کلاسی وابسته نیست:

    public static void affiche(Object obj){
        ….
    }

کلاس Object والد همه کلاس‌های جاوا است. بنابراین وقتی می‌نویسیم:

    public class personne

ما به‌طور ضمنی در حال نوشتن هستیم:

    public class personne extends Object

در نتیجه، هر شیء جاوا شامل یک جزء از نوع 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'object شناسایی می‌کند. در مثال قبلی ما چه اتفاقی می‌افتد:

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

سیستم باید دستور System.out.println(e.toString()) را اجرا کند، که در آن e یک شیء معلم است. در سلسله‌مراتب کلاس‌ها به دنبال متد toString می‌گردد که به کلاس enseignant منتهی می‌شود، با شروع از آخرین کلاس:

  • در کلاس enseignant، متد toString() را پیدا نمی‌کند
  • در کلاس والد personne، متد toString() را پیدا نمی‌کند
  • در کلاس والد Object، متد toString() را پیدا می‌کند و آن را اجرا می‌کند

این موضوع با برنامه زیر نشان داده می‌شود:


// وارد کردن person;
// 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

یعنی شیء nom_de_la_classe@adresse_de_l. از آنجایی که این چندان واضح نیست، ممکن است وسوسه شوید که متدی با نام toString برای کلاس‌های personne و etudiant تعریف کنید که متد toString از کلاس والد QZXW2HTMLP000541Z را بازنویسی (override) می‌کند.QX. به جای نوشتن متدهایی که مشابه متدهای موجود 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);
  }//test1

   // گیرنده
  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());
  }//main

}// پایان کلاس

کلاس 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()
          بررسی می‌کند که آیا این شمارشگر (enumeration) عناصر بیشتری دارد.
 
شیء
element بعدی()
          عنصر بعدی این شمارش را بازمی‌گرداند، اگر این شیء شمارش حداقل یک عنصر دیگر برای ارائه داشته باشد.
 

هر کلاسی که این رابط را پیاده‌سازی کند، به صورت زیر اعلام خواهد شد

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 به شرح زیر است:


//کلاس‌های واردشده
// import notes;
// import Istats;
// import student;

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);
        }//برای
        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 را بازمی‌گردانند.

کلاس تست زیر:


//کلاس‌های واردشده
// import student;
// import Istats;
// import marks;
// import 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» ذخیره شده‌اند
        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، این رابط را پیاده‌سازی می‌کنند.


//کلاس‌های واردشده
// 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;
    }
}//کلاس

//کلاس‌های واردشده
// 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;
    }
}//کلاس

برای ساده‌سازی مثال، کلاس‌ها کاری جز پیاده‌سازی رابط Iexemple انجام نمی‌دهند. اکنون مثال زیر را در نظر بگیرید:


//کلاس‌های واردشده
// import class1;
// import class2;

//کلاس تست
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));
    }//محاسبه

     // تابع اصلی
    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
}//کلاس تست

تابع استاتیک calculer یک عنصر از نوع Iexemple را به عنوان پارامتر می‌پذیرد. بنابراین، این تابع می‌تواند برای این پارامتر، یک شیء از نوع classe1 یا از نوع classe2 را بپذیرد. این کاری است که در تابع main انجام می‌شود، با نتایج زیر:

17
21
127
201

بنابراین می‌توانیم ببینیم که این ویژگی شبیه به چندریختی (polymorphism) مشاهده‌شده در کلاس‌ها است. از این رو، اگر مجموعه‌ای از کلاس‌های Ci که از طریق ارث‌بری به یکدیگر مرتبط نیستند (و بنابراین نمی‌توان از چندریختی مبتنی بر ارث‌بری استفاده کرد) مجموعه‌ای از متدها با امضای یکسان را به اشتراک بگذارند، ممکن است مفید باشد که این متدها را در یک رابط (interface) به نام 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:


//کلاس‌های واردشده
// import Iexample;

//کلاس تست
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));
    }//محاسبه

     // تابع اصلی
    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
        // یک شیء Iexample دیگر
    Iexemple i3=new classe3();

        // فراخوانی‌های تابع استاتیک `calculate`
        calculer(4,3,i1);
        calculer(14,13,i2);
    calculer(24,23,i3);
    }//main
}//کلاس تست

ویژگی متمایز در کد نهفته است:


         // ایجاد دو شیء که رابط 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 است. بنابراین می‌توان اشیایی از نوع رابط ایجاد کرد. بسیاری از متدهای کلاس‌های جاوا اشیایی از نوع رابط 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();
}//اصلی
}//کلاس

در مثال بالا، یک کلاس instantiate می‌شود، نه یک interface. کلاسی که به صورت «on the fly» تعریف می‌شود، کلاس ناشناس (anonymous class) نامیده می‌شود. این روشی است که اغلب برای instantiate کردن اشیایی استفاده می‌شود که تنها هدفشان پیاده‌سازی یک interface است.

اجرای برنامه قبلی نتایج زیر را تولید می‌کند:

17
21
127
201
1047
2001

مثال قبلی از کلاس‌های ناشناس برای پیاده‌سازی یک رابط استفاده کرد. این‌ها همچنین می‌توانند برای مشتق‌سازی کلاس‌هایی که سازندهای پارامتریک ندارند، به کار روند. مثال زیر را در نظر بگیرید:

//کلاس‌های واردشده
// 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;
    }
};//تعریف کلاس ۳

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));
    }//محاسبه

   // متد اصلی
    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 یک اینترفیس نیست، بلکه یک کلاس است. بنابراین برای کامپایلر، این یک ارث‌بری از کلاس است. هر متدی که در بدنه کلاس ناشناس پیدا کند، متدهای هم‌نام در کلاس پایه را بازنویسی (override) خواهد کرد.

اجرای برنامه قبلی نتایج زیر را تولید می‌کند:

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 را وارد می‌کند. بنابراین گویی عبارت زیر در ابتدای هر برنامه وجود دارد:

import java.lang.*;

این عبارت چه معنایی دارد؟ این عبارت دسترسی به تمام کلاس‌های پکیج java.lang را فراهم می‌کند. کامپایلر فایل System.class را در آنجا پیدا خواهد کرد که کلاس System را تعریف می‌کند. ما هنوز نمی‌دانیم کامپایلر بسته java.lang را کجا پیدا خواهد کرد، و نیز یک بسته چه شکلی است. بعداً به این موضوع باز خواهیم گشت. برای ایجاد یک کلاس در داخل یک بسته، می‌نویسیم:

package paquetage;
//تعریف کلاس
...

برای مثال، بیایید کلاس personne را که قبلاً در مورد آن بحث کردیم، در یک پکیج ایجاد کنیم. ما istia.st را به عنوان نام پکیج انتخاب می‌کنیم. کلاس personne به این صورت در می‌آید:


// نام پکیجی که کلاس 'person' در آن ایجاد خواهد شد
package istia.st;

//کلاس 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;
    }

    // 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);
    }//main
}//کلاس 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)

اکنون نوبت ماشین مجازی جاوا است که نتواند کلاس istia/st/personne را پیدا کند. این ماشین در دایرکتوری جاری به دنبال آن می‌گردد، در حالی که اکنون در دایرکتوری mesClasses قرار دارد. بیایید نگاهی به گزینه‌های ماشین مجازی جاوا بیندازیم:

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

چندین دutz از آنها وجود دارد. یک فایل .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 او محاسبه می‌شود. جدول زیر را در نظر بگیرید:
۱۲٬۶۲۰٫۰
0
0
۱۳٬۱۹۰
0.05
۶۳۱
۱۵٬۶۴۰
0.1
۱٬۲۹۰٫۵
۲۴٬۷۴۰
0.15
۲٬۰۷۲٫۵
۳۱٬۸۱۰
0.2
۳٬۳۰۹٫۵
۳۹٬۹۷۰
0.25
۴٬۹۰۰
۴۸٬۳۶۰
0.3
۶۸۹۸.۵
۵۵٬۷۹۰
0.35
۹٬۳۱۶٫۵
۹۲٬۹۷۰
0.4
۱۲٬۱۰۶
۱۲۷٬۸۶۰
0.45
۱۶٬۷۵۴٫۵
۱۵۱٬۲۵۰
0.50
۲۳٬۱۴۷٫۵
۱۷۲٬۰۴۰
0.55
۳۰٬۷۱۰
۱۹۵٬۰۰۰
0.60
۳۹٬۳۱۲
0
0.65
۴۹٬۰۶۲

هر سطر دارای ۳ فیلد است. برای محاسبه مالیات I، به دنبال اولین سطری می‌گردیم که QFfield1 باشد. برای مثال، اگر QF = 23000 باشد، سطر را پیدا خواهیم کرد

    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» به صورت زیر تعریف خواهد شد:


// ایجاد کلاس 'taxes'

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]);
    }//محاسبه
}//کلاس

یک شیء مالیاتی با داده‌های لازم برای محاسبه مالیات یک مؤدی ایجاد می‌شود. این بخش ثابت شیء است. پس از ایجاد این شیء، می‌توان متد **calculate** آن را بارها برای محاسبه مالیات مؤدی بر اساس وضعیت تأهل (متأهل یا مجرد)، تعداد فرزندان و حقوق سالانه‌اش فراخوانی کرد.

یک برنامه آزمایشی ممکن است به این شکل باشد:


//کلاس‌های واردشده
// واردات مالیات‌ها;
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;
                }//اگر
                 // بررسی اعتبار پارامترها
                // متأهل
                String marié=args[0].toLowerCase();
                if (! marié.equals("o") && ! marié.equals("n")){
                    System.err.println(syntaxe+"\nArgument marié incorrect : tapez o ou n");
                    continue;
                }//اگر
                 //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;
                }//اگر
                 // حقوق
                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");
                 // مالیات‌دهنده بعدی
            }//در حالی که
        }//اصلی
    }//کلاس

در اینجا مثالی از اجرای برنامه قبلی آورده شده است:

E:\data\serge\MSNET\c#\مالیات‌ها\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 :