آموزش زبان برنامه نویسی جاوا: Constructor
اجازه دهید ابتدا مروری بر مفاهیم گذشته کنیم. همانطور که قبلا گفته شد، در تمام زبانهای برنامه نویسی، یک سری رسم و رسومات وجود دارد که برنامه نویسان آن زبان از آنها پیروی میکنند. پیروی کردن از این قواعد اجباری نیست و اگر آنها را رعایت نکنیم، باز هم برنامهی ما به درستی کامپایل و اجرا میشود. اما چه خوب است که آنها را رعایت کنیم. نمونهای از این قواعد برای نام گذاری کلاسها، متغیرها و متدها هستند. یعنی گفته میشود ابتدا اینکه نام کلاسها فعل نباشد، بلکه اسم باشد. مثلا کلاسی با نام ShowSomething میتوان ایجاد کرد، اما نام مناسبی نیست و بهتر است از نامهایی همچون MainApp, Time و ... که اسم هستند استفاده کنیم (البته نام کلاس باید متناسب با کاری که کلاس قرار است انجام دهد باشد). همچنین الگویی وجود دارد با نام «کوهان شتر» یا «CamelCase» که گفته میشود برای نام گذاری کلاسها از این الگو استفاده شود. به این صورت که حرف اول نام کلاسها، با حرف بزرگ الفبای انگلیسی شروع شود و اگر نام کلاس مرکب بود (چند کلمهای)، حرف اول کلمهی بعدی نیز با حرف بزرگ الفبای انگلیسی شروع شود. مثل MainApp.
برای نام گذاری متغیرها و متدها هم باید از همین الگو (کوهان شتر) استفاده شود، با این تفاوت که حرف اول آنها، حرف کوچک انگلیسی باشد و از ادامهی آن، از الگوی CamelCase استفاده شود. همچنین برای نام گذاری متدها، از فعل استفاده شود. مثلا نام showSomething نامی بسیار مناسب برای یک متد است.
با توجه به قوانین نام گذاری کلاسها، متغیرها و متدها که در بالا گفته شد، استثناهایی هم وجود دارد. یکی از آن استثناها، نام سازنده یا Constructor است. سازنده یک متد است و اگر بخواهیم قوانین نام گذاری را رعایت کنیم، باید نام سازنده با حرف کوچک انگلیسی شروع شود، اما از آنجا که نام سازنده با نام کلاس یکی است، بنابراین سازنده تنها متدی است که با حرف بزرگ انگلیسی شروع میشود.
اجازه دهید یک مرور کلی هم در مبحث متدها کنیم. متدها (که در بعضی از زبانهای برنامه نویسی به آن تابع یا Function گفته میشود)، مانند یک کارخانه هستند که ابتدا دادههایی را از ورودی دریافت میکنند، روی دادهها پردازشهایی انجام میدهند و سپس خروجی را تولید میکنند.
البته در برنامه نویسی ممکن است متدها ورودی دریافت یا خروجی تولید نکنند. متدها دو نوع هستند:
1) دستهی اول متدهایی هستند که مقدار برگشتی ندارند. یعنی به صورت void تعریف شدهاند. کد زیر:
package ir.zoomit;public class Test { public void showSomething() { System.out.println("Java"); }}
در کد بالا متدی با نام showSomething تعریف شده است که مقدار برگشتی آن void است و بدنهی سادهای را هم پیادهسازی کرده است (کلمهی Java را در خروجی استاندارد چاپ میکند).
2) دستهی دوم متدهایی هستند که مقدار برگشتی دارند، یعنی از نوع int, double, String و ... هستند (void نیستند). کد زیر:
package ir.zoomit;public class Test { public int sum() { return 2 + 2; }}
متد فوق از نوع عدد صحیح (int) تعریف شده است که باید حتما با استفاده از کلیدواژهی return مقداری صحیح را برگرداند ( این متد عدد صحیح 4 را بر میگرداند).
متد زیر هم یک متد از نوع void است که دو پارامتر دریافت میکند. کد زیر:
package ir.zoomit;public class Test { public void showSomething(int a, double b) { System.out.println("Result is: " + (a + b)); }}
متد فوق هم دو پارامتر دریافت میکند، یکی از نوع عدد صحیح و دیگری از نوع عدد اعشاری و سپس جمع آن دو عدد را در خروجی استاندارد (کنسول) نمایش میدهد.
نکته: به پرانتز گذاری قسمت (a + b) توجه کنید.
هنگام فراخوانی متد فوق هم، باید دو عدد (یکی صحیح و دیگری اعشاری) به متد ()showSomething پاس دهیم. کد زیر:
package ir.zoomit;public class MainApp { public static void main(String[] args) { Test t = new Test(); t.showSomething(5, 5.8); }}
اگر هنگام فراخوانی متد، اعدادی برای پارامترهای متد در نظر نگیریم، با خطای کامپایل مواجه میشویم.
همانطور که گفته شد، کانستراکتور هیچ نوع برگشتی ندارد (حتی void)، همنام کلاس است و میتواند صفر تا چند پارامتر داشته باشد. کد زیر:
package ir.zoomit;public class Test { // Constructor public Test() { }}
همانطور که مشاهده میکنید، در کد فوق کلاسی تعریف کردهایم با نام Test و برای این کلاس سازندهای در نظر گرفتهایم، نام سازنده دقیقا همنام با نام کلاس است.
ما اگر در برنامههای خود Constructor را بنویسیم، ماشین مجازی جاوا سازندهای را که ما تعریف کردهایم در نظر میگیرد. اما اگر تعریف نکنیم، JVM به صورت خودکار سازندهای بدون پارامتر فراخوانی میکند که به آن سازندهی پیش فرض یا Default Constructor میگویند. توجه داشته باشید که Default Constructor در زبانهای برنامه نویسی دیگر (مثل ++C)، به سازندههایی گفته میشود که هیچ پارامتری ندارند.
اما کاربرد Constructor
سازندهها برای مقداردهی اولیه به فیلدها و شیهای کلاس به کار میروند. به کد زیر توجه کنید:
package ir.zoomit;public class Test { int number; String str; boolean b;}
در اینجا ما یک کلاسی تعریف کردهایم با نام Test که این کلاس سه فیلد (سه ویژگی یا Property) دارد. همانطور که مشاهده میکنید این سه فیلد مقداردهی نشدهاند. حالا در کلاس اصلی (کلاسی که متد main در آن پیادهسازی شده است) میخواهیم از روی کلاس Test یک شی (آبجکت) ایجاد کنیم و سپس مقادیر این سه فیلد را در خروجی استاندارد چاپ کنیم. کد زیر:
package ir.zoomit;public class MainApp { public static void main(String[] args) { Test t = new Test(); System.out.println(t.number); System.out.println(t.str); System.out.println(t.b); }}
اگر برنامهی فوق را اجرا کنیم با خروجی زیر مواجه میشویم:
0nullfalse
همانطور که مشاهده میکنید مقادیری پیش فرض برای فیلدها در نظر گرفته شده است. اما نکته اینجا است که ما این مقادیر را تعیین نکردهایم (ما هیچ کدام از فیلدها را مقداردهی نکردیم). این مقدار دهی توسط سازنده پیش فرض انجام شده است. یعنی سازندهی پیش فرض به صورت خودکار توسط JVM فراخوانی شده است و برای فیلدهای کلاس مقادیر اولیه در نظر گرفته است. مقدار پیش فرض دادهی اولیه boolean برابر با false است. دادههایی از نوع Reference مثل کلاس String مقدار null یا هیچ یا پوچ دارند (دقت کنید مقدار صفر با null تفاوت دارد) و سایر Primitive Data Typeها مثل: int, long, double, char و ... مقدار پیش فرض صفر دارند.
توجه کنید اگر در کلاس Test سازنده را تعریف میکردیم و آن را پیادهسازی نمیکردیم (بدنهای برای آن در نظر نمیگرفتیم)، باز هم با همین نتیجه مواجه میشویم. در واقع در بدنهی سازندهی پیش فرض، فیلدهای کلاس با مقادیر پیش فرض مقدار دهی میشوند. چیزی شبیه کد زیر:
package ir.zoomit;public class Test { int number; String str; boolean b; public Test() { number = 0; str = null; b = false; }}
اما کاربرد Constructor فقط این نیست. اگر به آموزش Encapsulation مراجعه کنید، در آن آموزش ما یک کلاس با نام Time تعریف کردیم که کدهای آن کلاس به صورت زیر است:
package ir.zoomit;public class Time { private int hour; // 0-23 private int minute; // 0-59 private int second; // 0-59 public setTime(int h, int m, int s) { setHour(h); setMinute(m); setSecond(s); } public int getHour() { return hour; } public void setHour(int h) { hour = (h >= 0 && h < 24) ? h : 0; } public int getMinute() { return minute; } public void setMinute(int m) { minute = (m >= 0 && m < 60) ? m : 0; } public int getSecond() { return second; } public void setSecond(int s) { second = (s >= 0 && s < 60) ? s : 0; } @Override public String toString() { return hour + ":" + minute + ":" + second; }}
در این کلاس ما متدی با نام setTime تعریف کردیم که با یکبار فراخوانی آن میتوانستیم سه فیلد کلاس یعنی ساعت، دقیقه و ثانیه را مقدار دهی کنیم. کلاس Time در صورتی قابل استفاده است که هر سه فیلد آن مقداردهی شده باشد. اما فرض کنید که برنامه نویس فراموش کند که این متد را فراخوانی کند، در این صورت باید چه کاری انجام دهیم؟ بهترین راه حل این است که کلاس را به گونهای طراحی کنیم که هر زمانی که برنامه نویس خواست از روی کلاس آبجکتی ایجاد کند، در همان لحظه هم مقادیری را برای فیلدهای کلاس در نظر بگیرد، در غیر اینصورت برنامه با خطای کامپایل مواجه شود. برای این کار باید از سازنده یا Constructor استفاده کنیم. به کد زیر توجه کنید:
package ir.zoomit;public class Time { private int hour; // 0-23 private int minute; // 0-59 private int second; // 0-59 public Time(int h, int m, int s) { setHour(h); setMinute(m); setSecond(s); } public int getHour() { return hour; } public void setHour(int h) { hour = (h >= 0 && h < 24) ? h : 0; } public int getMinute() { return minute; } public void setMinute(int m) { minute = (m >= 0 && m < 60) ? m : 0; } public int getSecond() { return second; } public void setSecond(int s) { second = (s >= 0 && s < 60) ? s : 0; } @Override public String toString() { return hour + ":" + minute + ":" + second; }}
کلاس Time را اصلاح کردیم. یعنی متد setTime را از آن حذف کردیم و سپس سازندهای با سه پارامتر برای آن در نظر گرفتیم. حالا اگر در کلاس اصلی اقدام به ساخت یک شی از روی کلاس Time کنیم، مجبور میشویم که مقادیری برای پارامترهای سازنده در نظر بگیریم، در غیر این صورت با خطای کامپایل مواجه میشویم. کد زیر:
package ir.zoomit;public class MainApp { public static void main(String[] args) { Time now = new Time(10, 21, 55); System.out.println(now); }}
همانطور که مشاهده میکنید دقیقا بعد از new کردن و نوشتن نام کلاس، باید مقادیری را در نظر بگیریم.
در یک کلاس میتوانیم چندین سازنده (Constructor) داشته باشیم. به این کار اصطلاحا Overloading Constructor میگویند. در مبحث پُلی مورفیزم (Polymorphism) با مفهوم overload و override کاملا آشنا میشوید (این دو مفهوم را با یکدیگر اشتباه نکنید).
Overload کردن متد یعنی اینکه متدی تعریف کنیم مثلا با نام showSomething، و دوباره در همان برنامه متد دیگری تعریف کنیم با همان نام show، اما با پارامترهای مختلف. کد زیر:
package ir.zoomit;public class Test { public void showSomething() { System.out.println("Java"); } public void showSomething(String name) { System.out.println(name); }}
همانطور که مشاهده میکنید، دو متد با نام showSomething تعریف کردیم که متد اول بدون پارامتر است و متد دوم دارای یک پارامتر است. در اینجا متد showSomething را اصطلاحا Overload کردهایم.
از آنجا که سازنده نیز یک متد خاص است، بنابراین میتوان Constructor را نیز Overload کرد. در کلاس Time یک سازنده وجود دارد و برای اینکه بخواهیم از روی این کلاس آبجکتی ایجاد کنیم، حتما باید مقادیری را برای آن در نظر بگیریم. اما فرض کنید در جایی از برنامه فقط میخواهیم آبجکتی از روی کلاس ایجاد کنیم و نمیخواهیم مقادیر را مقداردهی کنیم. در این صورت باید یک سازندهی بدون پارامتر در کلاس تعریف کنیم تا هنگام ساخت آبجکت، سازندهی بدون پارامتر را فراخوانی کنیم.
انواع مقداردهی اولیه در جاوا
در جاوا سه روش برای مقداردهی اولیه برای فیلدها و اشیا وجود دارد:
- مقداردهی اولیهی درون خطی یا Inline Initialization
- بلوک مقداردهی اولیه یا Initialization Block
- سازنده یا Constructor
در ادامه با سه روش فوق آشنا میشوید.
نکته: توجه کنید که نامهای انگلیسی روشهای مقداردهی را به خاطر بسپارید. تقریبا از معادلهای فارسی فقط برای آموزش استفاده میشود.
روش اول که مقداردهی درون خطی است، ویژگیهای کلاس یا اشیا در همان خطی که تعریف میشوند، مقداردهی هم میشوند. کد زیر:
package ir.zoomit;public class Test { private int number = 10; // inline initialization}
در کد فوق ابتدا یک متغیر از نوع عدد صحیح (int) تعریف شده و سپس در همان خط مقداردهی شده است. به این روش، روش مقداردهی در خط گفته میشود. روش دوم که بلوک مقداردهی است، کد خود را در بین یک بلاک قرار میدهیم. کد زیر:
package ir.zoomit;public class Test { { Test[] t = new Test[10]; for (int i = 0; i < t.length; i++) { t[i] = new Test(); } }}
در کد فوق، بلوک مقداردهی اولیه با رنگ آبی مشخص شده است. همانطور که مشاهده میکنید فقط یک آکولاد باز و بسته قرار میدهیم و کدهای خود را در بین آنها مینویسیم. هنگامی که از روی این کلاس آبجکتی ساخته شود، این بلوک هم اجرا میشود.
روش سوم هم سازنده است که در قسمت قبل آموزش داده شد. اما اینجا نکتهای وجود دارد که اولویت اجرای این مقداردهیهای اولیه چگونه است؟ اولویت این مقداردهیها دقیقا به صورتی است که آموزش داده شده است. یعنی وقتی از روی یک کلاس آبجکتی ایجاد کنیم، ابتدا مقداردهیهای درون خطی اجرا میشوند، سپس بلوک مقداردهی و در آخر هم سازنده&
package ir.zoomit;public class MainApp { int num = number(); public int number() { System.out.println("Inline Initialization"); return 0; } // Initialization Block { System.out.println("Initialization Block"); } // Constructor public MainApp() { System.out.println("Constructor"); } public static void main(String[] args) { new MainApp(); }}
اگر برنامهی فوق را اجرا کنید با خروجی زیر مواجه میشوید:
Inline InitializationInitialization BlockConstructor
همانطور که مشاهده میکنید، به ترتیبی که گفته شد، اجرا شدند.
نکته پایانی
ما در شبکههای اجتماعی گروهی راهاندازی کردهایم که شما نیز میتوانید در این گروه عضو شوید و اگر سوالی در زمینه جاوا دارید، در این گروه مطرح کنید. به این لینک مراجعه کنید. همچنین در تلگرام هم کانالی راهاندازی شده که بیشتر جنبهی اطلاع رسانی این دورهی آموزشی را دارد. کسانی که هدفشان از یادگیری جاوا، برنامه نویسی اندروید است، تمام مطالب این دوره برای اندروید هم لازم است، مگر اینکه در خود آموزش به نکتهی غیر آن اشاره شده باشد. بنابراین بهتر است سوالاتی که جنبه فنی ندارند، از طریق تلگرام ارسال کنید، بررسی میکنیم و پاسخ میدهیم.