آموزش برنامه نویسی جاوا (بخش بیست و دوم: وراثت در جاوا)

استاندارد

در بخش قبل در مورد وراثت و مفاهیم آن توضیح داده شد.

یک کلاس پدر و یک کلاس فرزند را تعریف کردیم. و دیدیم که کلاس فرزند می توانست متغیرها و متدهای کلاس پدر را به ارث ببرد.

در این بخش و بخش آینده بیشتر با مفاهیم وراثت کار خواهیم کرد. این بخش بیشتر به نقش Modifier ها در وراثت می پردازیم.

ابتدا دوباره توضیحی مختصر در مورد وراثت می دهیم.

توارث شباهت بین دو کلاس را با استفاده از مفاهیم کلاس پایه (base) و کلاس مشتق شده (derived) بیان می کند.

در بخش قبلی به این دو کلاس، کلاس پدر و فرزند نیز گفته می شود. می توان به کلاس پایه، سوپر کلاس و به کلاس مشتق شده، ساب کلاس (Subclass) نیز گفت.

کلاسی که از آن ارث بری می شود کلاس پایه یا مبنا و کلاس وارث که خصوصیات کلاس پایه را به ارث می برد را کلاس مشتق شده می نامند.

کلاس پایه شامل کلیه خواص و رفتارهائی است که بین کلاس های مشتق شده مشترک است.

در واقع کلاس مشتق شده یا فرزند نوعی از کلاس پایه یا پدر است.

در بخش قبلی در مورد کلاس های اشکال هندسی توضیح داده شد. کلاس پدر، کلاس اشکال هندسی (Shape) و دو کلاس فرزند، کلاس های مستطیل (Rectangle) و کلاس دایره (Circle) می باشند.

هر متغیری که در کلاس Shape تعریف شود، انگار در کلاس های مستطیل و دایره نیز تعریف می شود. حال این دو کلاس نیز می توانند، متغیرها و متدهای خود را تعریف نمایند.

کلاس مشتق شده کلیه اعضای کلاس پایه را به ارث می برند اما اجازه دسترسی مستقیم به اعضای خصوصی کلاس پایه را ندارند و تنها از طریق توابع عمومی و سازنده به آنها دسترسی دارند.

وراثت و Modifier ها

می خواهیم نقش Modifier ها را در وراثت بررسی کنیم.

فرض کنید کد زیر که در بخش قبل نیز تعریف شد را دوباره تعریف کنیم.

کد زیر کلاس های عکس بالا را نشان می دهد.

این کد، کلاس Shape را تعریف کرده و سپس کلاس Circle را نیز به صورتی که از کلاس Shape به ارث ببرد تعریف می کند.

مشاهده می کنید که در قسمت Main ، ما یک شی از کلاس Circle ساخته ایم و این شی می تواند به اعضای کلاس پدر و اعضای کلاس Circle دسترسی داشته باشد.

public class JavaApplication1 {

static class Shape{
    String Color;
    int Area;

}
static class Circle extends Shape{
    int Radius;   
}

    public static void main(String[] args) {

        Circle MyCircle = new Circle();
        MyCircle.Color="Red";
        MyCircle.Area=113;
        MyCircle.Radius=6;
    } 
}

مشاهده می کنید که در قسمت Main ، ما یک شی از کلاس Circle ساخته ایم و این شی می تواند به اعضای کلاس پدر و اعضای کلاس Circle دسترسی داشته باشد.

می خواهیم نقش سه Modifier که Public و Private و Protected هستند را در ارث بری، بررسی کنیم.

همان طور که قبلا گفته شد، Public یعنی دسترسی در همه جا. پس کلاس فرزند هم جز همه جا هست. پس هر متغیری که در کلاس پدر به صورت Public تعریف شود، در کلاس فرزند نیز به ارث برده می شود.

برای بررسی دو مورد بعدی، کلاس Shape را به صورت زیر تغییر دهید:

static class Shape{
    private String Color;
    protected int Area; 
}

متغیر اول یعنی Color به صورت private تعریف شده است.

اگر به خط

MyCircle.Color="Red";

در کامپایلر مراجعه کنید، متوجه می شوید که اشکال گرفته است. یعنی متغیر Color دیکر در کلاس فرزند (Circle) قابل دسترس نیست.

در توارث خصوصی کلاس مشتق شده کلیه اعضای کلاس پایه را دارا خواهد بود اما به صورت مخفی و اعضای عمومی کلاس پایه اعضای خصوصی کلاس مشتق شده خواهند شد.

بنابراین یک شی به عنوان یک نمونه از کلاس نمی تواند به اعضای کلاس پایه دسترسی پیدا کند.

پس بهتر است متغیر و یا متدی که نمیخواهید در شی ای از کلاس فرزند به ارث برده شود ، به صورت Private تعریف نمایید.

نکته: توارث خصوصی برای پنهان کردن لایه زیرین پیاده سازی کلاس پایه مفید است.
نکته: در توارث خصوصی کلیه اعضای عمومی کلاس پایه خصوصی می شوند. اگر می خواهید عضوی قابل رویت شود کافی است نام آن را (بدون آرگومان و مقدار برگشتی) در بخش public کلاس مشتق شده ذکر کنید.

با ذکر کلمه protected قبل از متغیر و متد ها در کلاس پایه، به توارث محافظت شده می رسیم.

اعضای خصوصی همیشه خصوصی هستند اما گاهی می خواهید اعضائی را از خارج مخفی کنید ولی در کلاس مشتق شده قابل رویت باشند.

کلمه protected می گوید که اعضای محافظت شده برای هر کسی که از این کلاس ارث می برد قابل دسترس است و برای بقیه خصوصی است.

نقش modifierه Protected نیز در ارث بری مشخص می شود. زیرا متغیر و یا متدی که به صورت protected تعریف شوند، فقط در کلاس فرزند قابل دسترس خواهند بود.

پس در اینجا، متغیر Area فقط در کلاس Shape و کلاس Circle به ارث برده می شود.

این کار یک راهکار امنیتی در برنامه نویسی است.

نکته: در کلیه حالات اعضای خصوصی کلاس پایه در وراثت شرکت نمی کنند و خصوصی باقی می مانند.

نکته: معمولا توارث عمومی است تا رابط کلاس پایه همچنان رابط کلاس مشتق شده باشد.

نکته: توارث محافظت شده خیلی استفاده نمی شود و فقط برای تکمیل زبان برنامه نویسی است.

نکته: مناسب ترین روش این است که اعضای داده ای کلاس را صورت خصوصی تعریف کنید تا امکان تغییر پیاده سازی زیرین حفظ شود.

سپس به وارثین کلاس مجوز دسترسی کنترل شده ای به توابع عضو محافظت شده بدهید.

برای این که مثالی عملی ببینید، کد بالا را خط به خط با چیزهایی که گفته شد بررسی می کنیم:

public class JavaApplication1 {
// تعریف کلاس اشکال هندسی که در اینجا کلاس  پایه است
static class Shape{
    private String Color; // این متغیر فقط در همین کلاس و متدهای همین کلاس قابل دسترس است
    protected int Area; // این متغیرمحافظت شده است یعنی فقط در کلاس خودش و کلاس فرزند قابل دسترس است
}
// تعریف کلاس دایره که از کلاس پایه به ارث می برد
static class Circle extends Shape{
    int Radius;   // تعریف متغیری در کلاس فرزند
}
   // تابع اصلی برنامه
    public static void main(String[] args) {

        Circle MyCircle = new Circle(); // تعریف یک شی از نوع کلاس فرزند
        MyCircle.Color="Red"; // دسترسی به این متغیر خطا می دهد
        MyCircle.Area=113; // به این متغیر می توان دسترسی داشت
        MyCircle.Radius=6;
    } 
}

آموزش برنامه نویسی جاوا (بخش پانزدهم: معرفی Modifier ها در جاوا)

استاندارد

در این بخش به بررسی Modifierها در جاوا می پردازیم. اما Modifier چیست؟

Modifier ها کلمات کلیدی هستند که در ابتدای معرفی توابع (متدها) ، متغیرها و کلاس ها می آیند. یعنی نام آن ها در ابتدای متودها، متغیرها و کلاس ها می آیند و سپس نوع آن ها و… تعریف می شود.

زبان جاوا دارای Modifierهای فراوانی هستند که مورد از آنها عبارت اند از :

  • Modifierهای دسترسی
  • Modifierهای غیردسترسی

همان طور که گفته شد، برای استفاده از Modifier ها باید، نام آن ها را قبل از تعریف متغیر، متود و یا کلاس ها بیاورید. اگر یک پروژه ی جاوا در کامپایلرهای اکلیپس ، NetBeans و .. ایجاد کنید متوجه می شوید که تابع اصلی شما یعنی تابع Main از Modifier از نوع Public استفاده می کند:

public static void main(String[] args) {

// بدنه اصلی
    }

برای درک بهتر و چگونگی کارکرد و استفاده از Modifier ها، در ادامه به بررسی این دو دسته از Modifier ها می پردازیم.

Modifierهای دسترسی

با استفاده از این دسته از Modifier ها می توان سطح دسترسی را در برنامه های جاوا مشخص نمود. این مشخص کردن سطح دسترسی، می تواند برای متغیر ، متود و یا کلاس باشد.

در جاوا 4 نوع سطح دسترسی داریم:

1- قابل استفاده در همه جای برنامه:

این نوع دسترسی امکان استفاده از متغیر، متود و یا کلاس را در همه جای برنامه حتی خارج از پکیج را به جاوا می دهد. برای این که این سطح دسترسی را مشخص کنیم، از کلمه کلیدی Public استفاده می کنیم. پس کد تابع Main که در بالا آورده شد، می تواند مثال خوبی از این نوع سطح دسترسی باشد.

2- قابل دسترسی در کل پکیج:

این سطح دسترسی، باعث می شود که به آن متغیر، کلاس و یا متود، بتوان فقط در کل پکیج دسترسی داشت. این نوع سطح دسترسی به صورت پیش فرض فعال است و اگر از هیچ کلمه کلیدی استفاده نشود، منظور همین نوع سطح دسترسی است. برای مثال می توانید متغیر و متود زیر را مشاهده کنید:

String mySite = "Gsm Developers;";

void mySiteMethod(){
           //code
       }

3- قابل استفاده در کلاس:

این نوع سطح دسترسی باعث می شود که آن متغیر و یا تابع، فقط در آن کلاس قابل دسترسی باشند. و بیرون از آن قابل استفاده نباشند.

برای این سطح دسترسی از private استفاده می شود. این سطح دسترسی محدود کننده ترین سطح دسترسی است. استفاده از دسترسی private راه کپسوله کردن و پنهان کردن اطلاعات کلاس از سایر کلاس ها است. برای مثال می توانید کد زیر را مشاهده کنید:

public class myJava{
       private String mySite = "Gsm Developers";

       public String getmySite(){
           return mySite;
       } 
    }

در کد بالا متغیر mySite یک متغیر از نوع String بوده که فقط در همین کلاس قابل استفاده است. حال اگر بخواهید از این متغیر در جای دیگری استفاده نمایید، می توانید یک تابع از نوع Public را تعریف نموده و متغیر mySite را برگرداند. در جاهای دیگری غیر از این کلاس از این تابع برای استفاده از متغیر mySite می توان بهره برد.

4- قابل استفاده در subClass:

برای این که متغیر یا متدی در کلاس پدر قابل دسترس باشد و همچنین در کلاس فرزند نیز قابل استفاده باشد ولی در جای دیگر این امر ممکن نباشد از این سطح دسترسی استفاده می کنیم. برای مشخص کردن این سطح دسترسی باید از کلمه کلیدی protected قبل از تعریف شناسه استفاده کرد. همچنین در هنگام استفاده از این Modifier شناسه در داخل پکیج نیز قابل دسترسی خواهد بود. کلاس ها و اینترفیس ها را نمی توان به صورت protected تعریف کرد.

در مورد وراثت و استفاده از Modifier از نوع Protected در کلاس ها در بخش وراثت ها بیشتر صحبت خواهیم کرد.

Modifierهای غیر دسترسی

Modifierهایی که در ادامه در مورد آن ها صحبت می کنیم، ربطی به سطح دسترسی ندارند. و هرکدام معنی جداگانه ای دارند.

1- Static : 

در بخش معرفی کلاس ها در مورد ساخت شی از کلاس، صحبت کردیم. برای این که بتوانیم به متدهای کلاس ها، مثلا کلاس String دسترسی داشته باشیم، باید ابتدا شی را از نوع کلاس String تعریف می نمودیم.

با استفاده از Static می توان، به متدها و متغیر های کلاس، بدون تعریف نمودن شی از آن کلاس، دسترسی پیدا کرد. همچنین بین همه اشیا مشترک نیز می باشد. به عبارتی، بدون آن که یک شی از کلاس بسازیم، اگر متد یا متغیری را از نوع Static معرفی کنیم، با استفاده از نام کلاس قابل دسترسی خواهد بود. و لازم به ساخت شی نخواهد بود.

برای مثال Static ، کد زیر را می توانید مشاهده کنید:

package javaapplication1;

public class JavaApplication1 {

    public static class myJava{

        public static int myCount=0;

    }
    /**
     * @param args the command line arguments
     */
public static void main(String[] args) {

       myJava.myCount++;

       System.out.print(myJava.myCount);

    }

}

مشاهده می کنید که در تابع Main با استفاده از نام کلاس ، به متغیر دسترسی داشتیم و توانستیم مقدار آن را عوض کنیم. در هر جای برنامه نیز می توان به این متغیر دسترسی پیدا کرد.

همچنین اگر بعد از اجرای دستوری که در کد بالا، باعث شده مقدار متغیر myCount ، یک (1) شود، در جای دیگری از برنامه به این متغیر دسترسی پیدا کنیم، همین مقدار 1 را مشاهده می کنیم.

در صورتی که در ساخت اشیا از یک کلاس، متغیر هر تغییری که میکرد فقط مخصوص همان کلاس بود.

2- abstract :

این Modifier زمانی استفاده می شود که بخواهیم از کلاسی فقط به منظور ارث بری استفاده نماییم. یعنی کلاس مورد نظر پدر باشد. معمولا پیش می آید که در برنامه نویسی حرفه ای، بخواهیم کلاسی را به عنوان قالب، تعریف نماییم. مثلا متغیرها و متود هایی را برای کلاس تعریف کرده ولی این که در آن متود چه کدهایی اجرا می شود را در کلاسی که از آن به ارث می برد، مشخص می کنیم.

توجه داشته باشید که نمیتوانید از کلاسی که از نوع abstract ساخته شده است، شی به صورت new بسازید.

همچنین اگر بخواهید متدی را در کلاسی به صورت abstract تعریف نمایید، باید خود آن کلاس به صورت abstract تعریف شود.

در مورد ارث بری در بخش های آینده صحبت خواهد شد.

public abstract class myJava{

        public abstract void myMethod();
        public void myMethod2(){
        // myMethod2 کدهای تابع 
        }       
    }

در کد بالا مشاهده می کنید که myMethod از نوع abstract تعریف شده است و اگر بخواهید بدنه آن را در این کلاس تعریف نمایید، کامپایلر خطا می دهد. پس باید در فرزند آن، بدنه ی تابع myMethod تعریف شود.

همچنین myMethod2 نیز که abstract نیست، برعکس myMethod اگر بدنه ی آن را تعریف نکنیم به مشکل می خوریم!

3- final :

در اینجا با یک Modifier مهم و پرکاربرد، مواجه هستیم. زمانی که کلاسی از نوع final تعریف می شود، دیگر نمیتواند فرزندی داشته باشد.

اولین نکته ای که به ذهن شما باید برسد اینست که اگر کلاسی از نوع final تعریف شود، نمی تواند از نوع abstract نیز تعریف شود و برعکس.

کاربرد این Modifier برای تعریف متود به این گونه است که، اگر متودی در کلاس پدر از نوع final تعریف شود، دیگر در کلاس فرزند ( در صورتی که کلاس پدر خود final نباشد) نمی توانید، متود را باز نویسی (Override) نمایید.

همچنین کاربرد final برای متغیر، به این گونه است که، زمانی که متغیری از نوع final تعریف می شود، فقط یک بار قابل مقداردهی است. و دیگر در جایی از برنامه نمی توان به آن مقدار داد.

کلاس:

public final class myJava{
} // نمی تواند فرزند داشته باشد..

متود:

public final void myMethod(){
        //
        } // در کلاس فرزند نمی تواند، باز نویسی شود

متغیر:

این کد خطاست زیرا متغیر m که از نوع final استفاده شده است، نمی تواند دو بار مقدار بگیرد.

final int m = 5;
m=6;

کد زیر صحیح است:

final int m;
m=6;

4- transient :

از کلمه کلیدی transient برای این منظور استفاده می شود که به ماشین مجازی جاوا (JVM) بفهمانیم که هنگامی که محتویات شی را سریالیز می کند، متغیری که به صورت transient تعریف شده است را نادیده بگیرد.

5- synchronized :

این کلمه کلیدی بدین منظور مورد استفاده قرار می گیرد که یک متد در یک زمان فقط توسط یک Thread قابل دسترسی باشد. این Modifier همراه با Modifierهای دسترسی نیز به کار می رود.

5- volatile :

اگر بخواهیم همزمان از چند Thread در برنامه استفاده کنیم، از کلمه کلیدی volatile استفاده می کنیم. منظور از استفاده از این Modifier این است که وقتی از این Modifier برای یک متغیر استفاده می کنیم به ماشین مجازی جاوا می گوییم که همه ی thread ها اگر متغیر مورد نظر را در حافظه کش خود تغییر بدهند باید آن تغییر بر روی متغیر در حافظه هم اعمال شود.

دلیل این کار این است که اگر چند thread به صورت همزمان از یک متغیر استفاده می کنند مقدار متغیر داده قدیمی نداشته باشد و همیشه همگام پیش برویم. این Modifier فقط بر روی فیلدهای کلاس قابل استفاده است. یک متغیر volatile می تواند null باشد.

public class MyThread implements Runnable{
    private volatile boolean active;

    public void run(){
        active = true;
        while (active){ 
            // Code
        }
    }

    public void stop(){
        active = false; 
    }
}

اگر کد run را توسط یک thread اجرا کنیم و اگر متد stop را توسط یک thread دیگر اجرا کنیم ممکن است که در خط while(active) از متغیر active یک کپی در حافظه کش مربوط به thread اجرا کننده موجود باشد.

بنابراین حتی اگر متد stop هم اجرا شود برنامه متوقف نمی شود. اما در این برنامه به دلیل این که متغیر active به صورت volatile معرفی شده است، اگر در متد stop مقدار این متغیر برابر False قرار بگیرد در همان زمان متد run هم متوقف خواهد شد