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

استاندارد

سازنده یا Constructor

در هنگام ساخت یک شی از یک نمونه کلاس، می توان متغیر های یک شی را مقداردهی اولیه کرد.

تابع سازنده (Constructor) تابعی است که در حین نمونه گرفتن از یک کلاس یک مرتبه اجرا می شود. زیرا بسیار ساده تر و دقیق تر آن است که کلیه تنظیمات در زمان ایجاد اولیه شی انجام شود.

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

معمولا بعضی از اعضای کلاس قبل از استفاده نیاز به مقداردهی دارندکه همان طور که گفته شد، اين عمل توسط سازنده (constractor) انجام می گیرد که به شیء اين امکان را می دهد که هنگام ايجاد مقداردهی شود.

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

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

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

سازندگان کمی بنظر عجیب می آیند زیرا فاقد نوع برگشتی و حتی فاقد void هستند.

در زبان های شی گرا مثل سی شارپ و جاوا چون همه چیز بر پایه شی گرایی و کلاس هست شما وقتی یک کلاس می سازید میتونید با استفاده از سازنده ها متغیر ها رو مقدار دهی کنید.

وقتی شما یکی شی از کلاس مورد نظرتون می سازین(new می کنید!) کامپایلر به سراغ سازنده ی شما می رود که اگه نباشه سازنده ی پیش فرض فراخونی میشه که بدون پارامتره و شما میتونید از سازنده استفاده کرده و اونطوری که دلتون می خواد متغیر هاتون رو مقدار دهی کنید و از همون اول برای شی ساخته شدتون بفرستید .

تابع سازنده می تواند دارای پارامتر باشد بنابراين زمان ايجاد شیء می توان به متغيرهای عضو مقادير اوليه داد. برای ارسال آرگومان به تابع سازنده بايد هنگام تعريف شیء مقدار آرگومان بعد از نام شیء درون پرانتز قرار گيرد.

یک کلاس می تواند دارای چند سازنده با پارامترهای مختلف باشد. بهتر است همیشه حداقل یک سازنده حتی اگر خالی باشد ساخته شود.

برای تابع سازنده مقدار برگشتی ذکر نمی شود (حتی Void).

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

public class JavaApplication1 {

static class myClass{
    private int a;
    private char b;
// تابع سازنده
    myClass(){
    this.a=1; // مقدار دهی به متغیر در تابع سازنده
    this.b='a'; // مقدار دهی به متغیر در تابع سازنده
    }
}    
    public static void main(String[] args) {
        myClass newObject = new myClass(); // ایجاد شی
        System.out.print(newObject.a);
        System.out.print(newObject.b);
    } 
}

خروجی کد بالا، 1a می باشد. زیرا شی به هنگام ساخت توسط سازنده ی خودش مقدار دهی می شود.

همچنین سازنده می تواند مقادیری را نیز به صورت پارامتر ورودی در هنگام ساخت شی ، از شی بگیرد.

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

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

public class JavaApplication1 {

static class myClass{
    int a;
    char b;
// ساخت تابع سازنده
    myClass(int m, char n){
        a = m;
        b = n;
    }
}    
    public static void main(String[] args) {
        myClass newObject = new myClass(5,'m'); // مقدار دهی اولیه فیلدهای شی با استفاده از سازنده
        System.out.print(newObject.a); // خروجی مقدار 5 را نشان می دهد
        System.out.print(newObject.b); را نشان می دهد m خروجی مقدار

    } 
}

constructor مشخص می کند که هنگام ایجاد شیئی از نوع کلاس ، چه اتفاقی رخ می دهد . constructor های خاص خودشان را در تعریف کلاس مشخص می کنند. اما ، اگر constructor به طور صریح مشخص نشود ، در آن صورت جاوا آن را به طور خودکار تامین می کند. در خصوص کلاس ذکر شده در بخش قبل نیز، همین طور است.

مخرب یا Destructor

تابع مخرب کلاس (destructor) کم و بیش عکس سازنده عمل می کند. یک مخرب وقتی فراخوانی می شود که یک شی از بین می رود.

در ++C یک مخرب مشابه سازنده ساخته می شود فقط قبل از اسم آن علامت مد (~)قرار می گیرد.

تابع مخرب اتوماتیک وقتی متغیر شیء از حوزه دسترسی خارج می شود (برای متغیرهای سراسری وقتی از تابع اصلی خارج می شود و برای متغیر محلی هنگام خروج از بلاک تابع) فراخوانی می شود.

مشابه سازنده ها تابع مخرب نيز نوع برگشتی ندارد.

توابع مخرب یا destructor کاربرد زیادی در مدیریت حافظه برنامه های نوشته شده در ++C دارند.

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

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

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

از آنجا که زمان عملکرد Grabage Collector نا مشخص است، با استفاده از دستورات gc و runFinalization میتوان ماشین مجازی را ملزم به آزاد سازی حافظه (جمع آوری زباله ها از حافظه) نمود.

مثال زیر نحوه انجام این کار را نشان میدهد:

import java.util.ArrayList;
 
public class Samples  {
    public static void main(String[] args){
        long mem = Runtime.getRuntime().freeMemory();
 
        SampleClass c1 = new SampleClass();
        SampleClass c2 = new SampleClass();
        SampleClass c3 = new SampleClass();
 
        System.out.println(String.format("Used memory: %d KB", (mem - Runtime.getRuntime().freeMemory())/1024));
        mem = Runtime.getRuntime().freeMemory();
 
        c1 = c2 = c3 = null;
 
        System.gc();
        System.runFinalization();
 
        System.out.println(String.format("Released memory: %d KB", (Runtime.getRuntime().freeMemory() - mem)/1024));
    }
}
 
class SampleClass {
    private ArrayList<Double> _obj;
 
    public SampleClass() {
        _obj = new ArrayList<Double>();
        for (int i=0; i<1000000; i++)
            _obj.add(Math.random());
 
        System.out.println("Created");
    }
 
    public void finalize() {
        _obj.clear();
        _obj = null;
        System.out.println("Finalized");
    }
 
}

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

استاندارد

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

همچنین نحوه مقدار دهی به اجزای یک شی ، مورد دیگری بود که به آن پرداختیم.

حال پس از معرفی modifier ها و متدها بار دیگر به مبحث مهم کلاس ها پرداختیم.

زیرا مفاهیم بسیاری در مورد کلاس ها وجود دارد که هر برنامه نویسی در جاوا باید آن ها را بداند.

شی گرایی و کلاس

برنامه نویسی شیءگرائی (object oreinted programming) وسیله ای برای مدل کردن صحیح دنیای واقعی با استفاده از اشیا (objects) در برنامه و استفاده مجدد از کد است.

یک شی در برنامه دقیقا همان طور تعريف می شود که در دنيای واقعی است؛ خواص معینی دارد که آن را توصیف می کند و متدهایی که می توانید برای انجام کار معینی روی شیء استفاده کنید.

هدف کلی Java اضافه کردن شیءگرائی به زبان برنامه نویسی است.

یک شیء برای نگهداری داده استفاده می شود. داده و توابعی که روی داده کار می کنند به هم مربوط هستند بنابراين داده و توابع هردو با هم دریک بسته قرار می گیرند.

شیءگرائی بيشتر روی داده تاکيد دارد تا عمليات و توابعی که روی داده کار می کنند.

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

شرحی از داده ها و توابعی که می توانند روی داده کار کنند را کلاس (class) می نامند.

کلاس را به عنوان الگوئی برای توليد شیء می توان تصور کرد. کلاس در واقع يک نوع داده user-defined است.

اشياء نمونه هائی از کلاس ها هستند که در زمان اجرا ايجاد می شوند.

چهار مفهوم اصلی وجود دارند که اساس برنامه نویسی شیءگرائی را می سازند و توسط کلاس ها ارائه می شوند. این مفاهیم انتزاع (abstraction)، کپسوله کردن (encapsulation)، توارث (inheritance) و چندریختی (polymorphism) هستند.

انتزاع:

شیء گرائی ابزاری را برای برنامه نویس فراهم می کند که اجزای فضای مسئله را توسط اشيا نمایش دهد. مسئله به بخش های تشکيل دهنده تجزيه می شود.

هر مولفه يک شیء می شود که شامل داده های مرتبط و دستورالعمل های خود است.

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

مثال. درباره خصوصیات کلی وسيله نقليه بدون سروکارداشتن با یک وسيله و مدل خاص می توان بحث کرد. يک نمونه شیء MyVehicle از کلاس Vehicle که خواص کلی و توابع وسايل نقليه را در بر دارد می توان ايجاد کرد. تابع Print مشخصات کلی وسيله نقليه را نمايش می دهد.

Vehicle MyVehicle;
MyVehicle.Print();

کپسوله کردن:

قرار دادن داده و توابعی که روی داده کار می کنند را در یک بسته کپسوله کردن می گویند.

در برنامه نویسی رویه گرا (مشابه آنچه تا کنون انجام می داديد) معلوم نیست چه تابعی روی چه متغیری کار می کند. در برنامه های پیچیده تر این روابط تیره تر می شوند. در برنامه نویسی شیءگرائی داده و توابع مربوط به آن، که اغلب متد (method) ناميده می شوند، در يک بسته به نام کلاس قرار می گیرند بنابراین کاملا مشخص است چه تابعی روی چه داده ای کار می کند.

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

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

وراثت:

یکی ديگر از جنبه های مفید برنامه نویسی شیءگرائی قابلیت استفاده مجدد از کد است.

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

توارث اجازه می دهد کلاس جديدی شامل کليه داده ها و توابع کلاس (های) پياده سازی شود.

کلاس موجود را کلاس پايه (base) و کلاس جديد که اعضای کلاس پايه را به ارث می گيرد را کلاس مشتق شده (derived) می نامند.

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

حال این اشکال ممکن است، دایره، مربع و یا مستطیل را شامل شود که ممکن است هر يک از آنها احتياج به اطلاعات يا توابع اضافی داشته باشند.

بنابراين می توان برای هر کدام کلاس های فرعی را تعريف کرد که خواص کلاس Shape را به ارث می برند علاوه براين که دارای فيلدهای جديد ديگری هم هستند.

شکل زیر رابطه بین این کلاس ها را نشان می دهد. و نشان می دهد که همه ی آن ها از کلاس Shape به ارث می برند.

 

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

چند ریختی:

يک تابع در سلسله مراتب کلاس ها و زير کلاس ها می تواند به طرق مختلف پياده سازی شود و شکل های متعددی بگیرد.

چندریختی یک رابط مشترک را برای پیاده سازی های مختلف از يک تابع در اختيار می گذارد که برای اشیا تحت شرایط مختلف متفاوت عمل کند.

نگاهی دقیق تر به ایجاد شی با استفاده از new

اگر بخواهیم نگاهی دقیق تر به تعریف اشیا با عملگر new داشته باشیم باید بگوییم که فرآیند تعریف یک شی در جاوا به دو قسمت تقسیم می‌شود:

  1. تعریف متغیری برای نگهداری آدرس شی در حافظه
  2. ایجاد شی در حافظه و نسبت‌دادن آن به متغیر تعریف‌شده

مثلا فرض کنید Class زیر را داشته باشیم:

class myClass{
    int a;
    char b;
}

میخواهیم شی از کلاس myClass تعریف کنیم و به دو مورد بالا آن را تعمیم دهیم. به شکل زیر نگاه کنید:

عملگر new ، حافظه هر شی را به صورت پویا تخصیص می دهد.

شکل کلی آن در ذیل نشان داده شده است :

ClassVar ObjName= new ClassName();

ClassVar ، متغیری از نوع کلاسی است که ایجادمی شود.

ClassName نام کلاسی است که نمونه ای از آن ایجاد می شود.

همان طور که می دانید برای ایجاد متغیر ما نیازی به عملگر new داریم. چرا که انواع داده های پایه جاوا به صورت شی پیاده سازی نمی شوند. بلکه به صورت متغیرهای «معمولی» پیاده سازی می شوند.

این کار به خاطر بازدهی بیشتر انجام می گیرد.

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

با عدم تحمیل سرباز خاص شیء ها به انواع داده های پایه ، جاوا قادر به پیاده سازی کارآمدتر انواع داده های پایه می شود.

در آینده ، با آن دسته از نگارشهای «شیئی» انواع داده های پایه آشنا خواهید شد که برای استفاده در شرایطی مهیا شده اند که همان نوع شیء ها مورد نیاز می باشند.

مهم است به خاطر داشته باشید که new حافظه شیء ها را در طی اجرا ایجاد کنند. اما ، از آنجایی که حافظه محدوداست، این احتمال وجود دارد که new به دلیل عدم وجود حافظه کافی نتواند حافظه لازم برای یک شیء را تخصیص دهد.

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

یعنی ، هر کلاس نوعی چارچوب منطقی ایجاد می کند که رابطه بین اعضایش را تعریف می کند.

وقتی شیئی از یک کلاس معین را تعریف می کنید، در واقع نمونه ای از آن کلاس ایجاد می کنید.

از این رو ، هر کلاس ، نوعی ساختار منطقی است.

هر شیء نیز نوعی واقعیت فیزیکی است (یعنی هر شیء فضایی را در حافظه اشغال می کند) . مهم است که این تمایز را به ذهن خود بسپارید.

متغیرها یا Field های کلاس

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

مگر آن که در بیرون از تابع، به صورت public تعریف شده باشند که آن هم خاصیت public بودن است که آن متغیر را به این صورت در میاورد و نه متد.

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

حال که با متدها و modifier ها آشنا شده اید به بررسی بیشتر متغیرهای کلاس می پردازیم.

می توانید متغیرهایی را در خارج از متدها تنظیم کنید که همه ی متدهای موجود در گروه شما می توانند ببینند.

این متغیرها، متغیرهای Field (یا متغیرهای Instance) نامیده می شوند. می توانید آنها را دقیقا به روش دیگر متغیرها تنظیم کنید.

متغیرهایی که در یک کلاس تعریف می کنید بهتر است Public نبوده و Private باشند.

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

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

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

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

static class myClass{
    private int a;
    private char b;
}

هر دو متغیر a و b به صورت private تعریف شده اند.

برای دسترسی به این متغیرها بهتر است به جای آن که آن ها را public اعلام نموده، یک متد به صورت public تعریف کرده و آن متد این متغیرها را برگرداند:

static class myClass{
    private int a;
    private char b;
    public int geta(){
    return a;
    }
    public char getb(){
    return b;
    }
}

به عنوان مثال، کلاس بالا، دو متد geta و getb متغیرهای a و b را برای شما بر می گردانند.

در بخش های آینده همچنان به مبحث Class ها می پردازیم.

بخش بعدی ، سازنده و مخرب را در Class ها بررسی می کنیم.