آموزش جاوا بخش دوازدهم

آموزش جاوا بخش دوازدهم

نگاهی دقیق تر به گذر دادن آرگومان ها 

در کل ، دو راه وجود دارد تا یک زبان کامپیوتری یک آرگومان را به یک زیر روال گذر دهد. اولین راه " فراخوانی بوسـیله مقـدار" ()call-by-value اســـت . ایـــن روش مقـــدار یـــک آرگومـــان را در پـــارامتر رســـمی زیـــر روال کپـــی مـــی کنـــد . بنـــابراین تغییراتی که روی پارامتر زیر روال اعمال می شود ، تاثیری بر آرگومانی که برای فراخوانی آن استفاده شده نخواهد داشت . دومـین راهـی کـه یک آرگومان می تواند گذر کند " فراخوانی بوسیله ارجاع ()call-byrefrence "است . در این روش ، ارجاع به یک آرگومان ( نه مقدار آن آرگومان ) به پارامتر گذر داده می شود . داخل زیر روال از این ارجاع برای دسترسی به آرگومان واقعی مشخص شـده در فراخـوانی استفاده می شود . این بدان معنی است که تغییرات اعمال شده روی پارامتر ، روی آرگومانی که برای فراخوانی زیر روال اسـتفاده شـده ، تـاثیر خواهد داشت .خواهید دید که جاوا از هر دو روش برحسب اینکه چه چیزی گذر کرده باشد ، استفاده می کنـد . در جـاوا ، وقتـی یـک نـوع ساده را به یک روش گذر می دهید ، این نوع بوسیله مقدارش گذر میکند . بنابراین ، آنچه برای پارامتری که آرگومان را دریافت میکند اتفاق بیفتد هیچ تاثیری در خارج از روش نخواهد داشت . بعنوان مثال ، برنامه بعدی را در نظربگیرید : 

 // Simple types are passed by value.
 class Test {
 void meth(int i, int j ){
 i *= 2;
 j /= 2;
 }
 }

 class CallByValue {
 public static void main(String args[] ){
 Test ob = new Test ();
 int a = 15, b = 20;
 System.out.println("a and b before call :" + a + " " + b);
 ob.meth(a, b);
 System.out.println("a and b after call :" ++ a + " " + b);
 }
 } 

خروجی برنامه فوق بقرار زیرمی باشد :  

a and b before call :15 20
a and b after call :15 20 

بخوبی مشاهده می کنید که عملیات اتفاق افتاده داخل meth ()هیچ تاثیری روی مقـادیرa وb و در فراخـوانی اسـتفاده شـده انـد، نخواهـد داشت . در اینجا مقادیر آنها به 30 و 10 تغییر نمی یابد . وقتی یک شی ئ را به یک روش گذر می دهید ، شـرایط بطـور مهیجـی تغییـر مـی کند زیرا اشیائ بوسیله ارجاعشان گذر داده می شوند . بیاد آورید که وقتی یک متغیر از یک نوع کلاس ایجاد می کنید ، شما فقط یک ارجاع به شی ئ خلق می کنید . بدین ترتیب ، وقتی این ارجاع را به یک روش گذر می دهید ، پارامتری که آن را دریافت مـی کنـد . بهمـان شـی ئ ارجاع می کند که توسط آرگومان به آن ارجاع شده بود . این بدان معنی است که اشیائ با استفاده از طریقه " فراخوانی بوسـیله ارجـاع " بـه روشها گذر داده می شوند. تغییرات اشیائ داخل روش سبب تغییر شیئی است که بعنوان یک آرگومان استفاده شده است . بعنوان مثال ، برنامـه بعدی را در نظربگیرید : 

 // Objects are passed by reference.

 class Test {
 int a, b;

 Test(int i, int j ){
 a = i;
 b = j;
 }

 // pass an object
 void meth(Test o ){
 o.a *= 2;
 o.b /= 2;
 }
 }

 class CallByRef {
 public static void main(String args[] ){
 Test ob = new Test(15, 20);

 System.out.println("ob.a and ob.b before call :" + ob.a + " " + ob.b);
 ob.meth(ob);
 System.out.println("ob.a and ob.b after call :" ++ ob.a + " " + ob.b);

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

ob.a and ob.b before call :15 20
ob.a and ob.b after call :30 10 

همانطوریکه می بینید ، در این حالت ، اعمال داخل meth () ، شیئی را که بعنوان یک آرگومان استفاده شده تحت تـاثیر قـرار داده اسـت . یک نکته جالب توجه اینکه وقتی یک ارجاع شی ئ به یک روش گذر داده می شود، خود ارجاع از طریق " فراخوانی بوسـیله مقـدار " گـذر داده می شود . اما چون مقداری که باید گذر داده شود خودش به یک شی ئ ارجاع می کند ، کپی آن مقدار همچنان به همان شـی ئ ارجـاع می کند که آرگومان مربوطه ارجاع می کند . یاد آوری : وقتی یک نوع ساده به یک روش گذر داده میشود اینکار توسط " فراخوانی بوسـیله مقدار " انجام میگیرد .اشیائ توسط " فراخوانی بوسیله ارجاع " گذر داده می شوند 

برگرداندن اشیائ 

یک روش قادر است هر نوع داده شامل انواع کلاسی که ایجاد میکنید را برگرداند .بعنوان مثال ، در برنامه بعدی روش incrByTen ()یک شی ئ را برمی گرداند که در آن مقدار a ده واحد بزرگتر از مقدار آن در شی ئ فراخواننده است . 

 // Returning an object.
 class Test {
 int a;

 Test(int i ){
 a = i;
 }

 Test incrByTen (){
 Test temp = new Test(a 10);
 return temp;
 }
 }

 class RetOb {
 public static void main(String args[] ){
 Test ob1 = new Test(2);
 Test ob2;
 ob2 = ob1.incrByTen ();
 System.out.println("ob1.a :" + ob1.a);
 System.out.println("ob2.a :" + ob2.a);
 ob2 = ob2.incrByTen ();
 System.out.println("ob2.a after second increase :" + ob2.a);
 } 
 } 

خروجی برنامه فوق بقرار زیرمی باشد :  

ob1.a :2
ob2.a :12
ob2.a after second increase :22 

همانطوری که مشاهده می کنید ، هر بار که incrByTen ()فراخوانده می شود ، یک شی ئ جدید تولید شـده و یـک ارجـاع بـه آن شـی ئ جدید به روال فراخواننده برگردان می شود . مثال قبلی یک نکته مهم دیگـر را نشـان میدهـد : از آنجاییکـه کلیـه اشـیائ بصـورت پویـا و بـا استفاده از new تخصیص می یابند ، نگرانی راجع به شیئی که خارج از قلمرو برود نخواهید داشت ، زیرا در این صورت روشـی کـه شـی ئ درآن ایجاد شده پایان خواهد گرفت . یک شی ئ مادامی که از جایی در برنامه شما ارجاعی به آن وجود داشته باشد ، زنده خواهد مانـد . وقتـی که ارجاعی به آن شی ئ وجود نداشته باشد ، دفعه مرمت خواهد شد. 

خود فراخوانی یا برگشت پذیری Recursion  

جاوا از خود فراخوانی پشتیبانی می کند . خود فراخوانی پردازشی است که در آن چیزی بر حسب خودش تعریف شـود . در ارتبـاط بـا برنامـه نویسی جاوا ، خود فراخوانی خصلتی است که به یک روش امکان فراخوانی خودش را می دهـد . روشـی کـه خـودش را فراخـوانی مـی کنـد موسوم به " خود فراخوانده " یا برگشت پذیر (recursive ) است . مثال کلاسیک برای خود فراخوانی محاسبه فاکتوریل یک رقم اسـت . فاکتوریل یک عدد N عبارت است از حاصلضرب کلیه اعداد از 1تاN ا . بعنوان مثال فاکتوریل 3 معادل 1 x2x3یا عدد 6 است . در زیـر نشان داده ایم چگونه می توان با استفاده از یک روش خود فراخوان ، فاکتوریل را محاسبه نمود : 

// A simple example of recursion.
 class Factorial {
 // this is a recursive function
 int fact(int n ){
 int result;
 if(n==1 )return 1;
 result = face(n-1 )* n;
 return result;
 }
 }
 class Recursion {
 public static void mane(String args[] ){
 Factorial f = new Factorial ();
 System.out.println("Factorial of 3 is " + f.face(3));
 System.out.println("Factorial of 4 is " + f.face(4));
 System.out.println("Factorial of 5 is " + f.face(5));
 }
 } 

خروجی این برنامه را در زیر نشان داده ایم :  

Factorial of 3 is 6
Factorial of 4 is 24
Factorial of 5 is 120 

اگر با روشهای خود فراخوان ناآشنا باشید ، آنگاه عملیات fact ()ممکن است تا حدی بنظرتان گیج کننده باشد . طرز کار آن را توضیح داده ایم . وقتی fact ()با یک آرگومان 1 فراخوانی می شود ، تابع مقدار 1 را برگردان می کند ، در غیـر ایـن صـورت ایـن تـابع حاصلضـرب َ (fact*n-1) را برگردان میکند. برای ارزیابی این غبارت fact ()را با 1-n فراخوانی می کنیم . این پردازش آنقدر تکرار می شود تا n مساوی 1باشد و فراخوانی های روش ، شروع به برگردان نمایند . 

برای درک بهتر نحوه کار روش fact ()اجازه دهید یک مثال کوتـاه بیـاوریم . وقتـی فاکتوریـل 3 را محاسـبه مـی کنیـد، اولـین فراخـوانی fact()سبب دومین فراخوانی با آرگومان 2 می شود . این فراخوانی سبب می شود تـا fact ()بـرای سـومین بـار بـا آرگومـان 1 فراخـوانی شود . این فراخوانی مقدار 1 را برگردان می کند که بعدا" در )2 مقدار n در فراخوانی دوم ) ضرب می شود. این نتیجه ( یعنی عدد 2 ( آنگاه به فراخوانی اصلی fact ()برگردان شده و در 3 ) مقدار اصلی ( n ضرب می شود . جواب کل عدد 6 است . اگر دسـتورات println () را در fact ()جایگـذاری نماییـد بسـیار جالـب خواهـد شـد چـون نشـان خواهـد داد کـه هـر فراخـوانی در چـه سـطحی اسـت و جوابهـای میانی چه مقادیری هستند . وقتی یک روش خودش را فراخوانی می کند ، حافظه پشته ها به متغیرهـای محلـی و پارامترهـای جدیـد تخصـیص داده می شود و کد روش نیز از همان اول با همین متغیرهای جدید اجرا می شود . یک خود فراخوانی ، کپی جدیدی از روش ایجاد نمی کند ، بلکه فقط آرگومانها جدید هستند . همچنانکه هر خود فراخوانی مقداری را برمی گرداند متغیرهـای محلـی و پارامترهـای قـدیمی از روی پشـته برداشته می شوند ، و اجرا در نقطه ای از فراخوانی داخل روش از سر گرفته خواهد شد . روشهای خود فراخوان را مـی تـوان تلسـکوپی نامیـد کـــه بـــاز و بســـته مـــی شـــوند . روایتهـــای خـــود فراخـــوانی بســـیاری از روالهـــا ، ممکـــن اســـت کمـــی کنـــدتر از روایتهـــای تکراری اجرا شوند و این بخاطر افزوده شدن انباشت فراخوانهای تابع اضافی است . بسیاری از خود فراخـوانی هـا بـه یـک روش ممکـن اسـت سبب سر ریز شدن یک پشته شوند . از آنجاییکه ذخیره سازی پارامترها و متغیرهای محلی روی پشته انجام می گیرد و هر فراخوانی جدید یک کپی جدید از این متغیرها بوجود می آورد ، این امکان وجود دارد که پشته خراب شود . اگر چنین اتفاقی بیفتد ، سیستم حین اجرای جاوا یک استثنائ را بوجود می آورد. اما احتمالا" نگرانی درباره این مسائل نخواهید داشت مگر آنکه یک روال خـود فراخـوانی از حالـت عـادی خـارج شود . 

مهمترین مزیت روشهای خود فراخوان این است که از آنها برای ایجاد روایتهای ساده ترو روشنتر از الگوریتمهایی که می توان بـا رابطـه هـای تکراری هم ایجاد نمود استفاده می شود . بعنوان مثال ، الگوریتم دسته بندی Quicksort در روش تکـراری (iterative ) بسـیار بسـختی پیاده سازی می شود . برخی مشکلات ، بخصوص مشکلات مربوط به Ai بنظرمی رسد که نیازمند راه حلهای خود فراخوانی هستند. در نهایت ، اینکه بسیاری از مردم شیوه های خود فراخوانی را بهتر از شیوه های تکراری درک می کنند . هنگام نوشـتن روشـهای خـود فراخـوان ، بایـد یک دستور if داشته باشید که روش را مجبور کند تا بدون اجرای فراخوان خود فراخوان ، برگردان نماید . اگر اینکار را انجام ندهید ، هر بـار که روش را فراخوانی کنید ، هرگز برگردان نخواهد کرد . 

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

دراینجا مثالی از خودفراخوانی را مشاهده کنید. روش خودفراخوانی printArray ()اولین عنصر i در آرایه values را چاپ می کند :  

 // Another example that uses recursion.
 class RecTest {
 int values[];
 RecTest(int i ){
 values = new int[i];
 }
 // display array -- recursivaly
 void printArray(int i ){
 if(i==0 )return;
 else printArray(i-1);
 System.out.println("[" +( i-1 )+ "] " + values[i-1]);
 }
 }
 class Recursion2 {
 public static void mane(String args[] ){
 RecTest ob = new RecTest(10);
 int i;
 for(i=0; i<10; i++ ) ob.values[i] = i;
 ob.printArray(10);
 }
 } 

 

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

[0] 0
[1] 1
[2] 2
[3] 3
[4] 4
[5] 5
[6] 6 

انباشتن روشها 

در جاوا این امکان وجود دارد که دو یا چند روش را داخل یک کلاس که همان نام را دارد تعریف نمود ، البته مادامیکه اعلان پارامترهای آن روش ها متفاوت باشد . در چنین شرایطی ، روشها را می گویند " انباشته شده " و این نوع پردازش را" انباشتن روش " می نامند . انباشـتن روش یکی از راههایی است که جاوا بوسیله آن" چند شکلی " را پیاده سازی می کند . اگر تا بحال از زبانی که امکان انباشتن روشها را دارد استفاده نکرده اید ، این مفهوم در وهله اول بسیار عجیب بنظر می رسد . اما خواهید دید که انباشتن روش یکی از جنبه هـای هیجـان انگیـز و سـودمند جاوا است . وقتی یک روش انباشته شده فراخوانی گردد ، جاوا از نوع و یا شماره آرگومانهـا بعنـوان راهنمـای تعیـین روایـت (Version ) روش انباشته شده ای که واقعا" فراخوانی می شود ، استفاده می کند . بدین ترتیب ، روشهای انباشته شده باید در نوع و یا شماره پارامترهایشان متفاوت باشند . در حالیکه روشهای انباشته شده ممکن است انواع برگشتی متفاوتی داشته باشند ، اما نوع برگشـتی بتنهـایی بـرای تشـخیص دو روایت از یک روش کافی نخواهد بود. وقتی جاوا با یک فراخوانی به یک روش انباشته شده مواجه می شود ، خیلـی سـاده روایتـی از روش را اجرا می کند که پارامترهای آن با آرگومانهای استفاده شده در فراخوانی مطابقت داشته باشند . در اینجا یک مثال ساده وجـود دارد کـه نشـان دهنده انباشتن روش می باشد : 

 // Demonstrate method overloading.
 class OverloadDemo {
 void test (){
 System.out.println("No parameters");
 }
 // Overload test for one integer parameter.
 void test(int a ){
 System.out.println("a :" + a);
 }
 // Overload test for two integer parameters.
 void test(int a, int b ){
 System.out.println("a and b :" + a + " " + b);
 }
 // Overload test for a double parameter.
 double test(double a ){
 System.out.println("double a :" + a);
 return a*a;
 }
 } 
 class Overload {
 public static void main(String args[] ){
 OverloadDemo ob = new OverloadDemo ();
 double result;
 // call all versions of test ()
 ob.test ();
 ob.test(10);
 ob.test(10, 20);
 result = ob.test(123.2);
 System.out.println("Result of ob.test(123.2 :)" + result);
 }
 } 

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

No parameters
a :10
a and b :10 20
double a :123.2
Result of ob.test(123.2 :)15178.2 

همانطوری که می بینید ، test()چهار بار انباشته شده است . اولین روایت پارامتری نمی گیرد ، دومین روایت یک پارامتر عدد صحیح می گیرد ، سومین روایت دو پارامتر عدد صحیح می گیرد و چهارمین روایت یک پارامتر double می گیرد . این حقیقت که چهارمین روایت test()همچنین مقداری را برمی گرداند، هرگز نتیجه حاصل از عمل انباشتن نیست ، چون انواع برگشتی نقشی در تجزیه و تحلیل انباشت ندارند .وقتی یک روش انباشته شده فراخوانی میشود،جاوا بدنبال تطبیقی بین آرگومانهای استفاده شده برای فراخوانی روش و پارامترهای آن روش می گردد . اما ، این تطابق نباید لزوما "همیشه صحیح باشد. در برخی شرایط تبدیل انواع خودکار جاوا میتواند نقشی در تجزیه و تحلیل انباشت داشته باشد . بعنوان مثال ، برنامه بعدی را در نظر بگیرید : 

 // Automatic type conversions apply to overloading.
 class OverloadDemo {
 void test (){
 System.out.println("No parameters");
 }
 // Overload test for two integer parameters.
 void test(int a, int b ){ 
 System.out.println("a and b :" + a + " " + b);
 }
 // Overload test for a double parameter.
 double test(double a ){
 System.out.println("Inside test(double )a :" + a);
 }
 }
 class Overload {
 public static void main(String args[] ){
 OverloadDemo ob = new OverloadDemo ();
 int i = 88;
 ob.test ();
 ob.test(10, 20);
 ob.test(i); // this will invoke test(double)
 ob.test(123.2); // this will invoke test(double)
 }
 } 

این برنامه خروجی زیر را تولید می کند No parameters :  

a and b :10 20
Inside test(double )a :88
Inside test(double )a :123.2

همانطوریکه مشاهده میکنید، این روایت از overloadDemoتعریف کننده test)int ) نمی باشد . بنابراین هنگامیکـه test ()همـراه بـا یک آرگومان عدد صحیح داخل overload فراخوانی می شود ، هیچ روش تطبیق دهنده پیدا نخواهد شد . اما جاوا می تواند بطـور خودکـار یک عدد صحیح را به یک double تبـدیل نمایـد و ایـن تبـدیل بـرای رفـع فراخـوانی مورداسـتفاده قـرار میگیـرد .بنـابراین ، بعـد از آنکـه test)int) پیدا نمی شود ، جاوا i را به double ارتقائ داده و آنگاه test)double ) را فراخـوانی مـی کنـد . البتـه اگـر test)int ) تعریف شده بود ، فراخوانی می شد . جاوا فقط در صورتی که هیچ تطبیق دقیقی پیدا نکند ، از تبدیل خودکار انواع استفاده می کند . انباشـتن روش از چند شکلی هم پشتیبانی می کند زیرا یکی از شیوه هایی است که جاوا توسط آن الگوی " یک رابط و چندین روش " را پیاده سازی مـــی کنـــد . بـــرای درک ایـــن مطلـــب ، مـــورد بعـــدی را درنظـــر بگیریـــد .در زبانهـــایی کـــه از انباشـــتن روش پشـــتیبانی نمی کنند ، هر روش باید یک اسم منحصر بفرد داشته باشد . اما غالبا" می خواهید یک روش را بـرای چنـدین نـوع داده مختلـف پیـاده سـازی نمایید . مثلا" تابع قدر مطلق را در نظربگیرید . در زبانهایی که از انباشتن روش پشتیبانی نمی کنند معمولا "سه یا چنـد روایـت مختلـف از ایـن تابع وجود دارد ، که هر یک اسم متفاوتی اختیار می کند . بعنوان نمونه ، در زبان C تابع abs ()قدر مطلق یک عدد صحیح را برمی گرداند ، labs()قدر مطلق یک عدد صحیح long را برمی گرداند ، fabs()قدر مطلق یک عدد اعشاری را برمی گرداند . از آنجاییکه زبان C از انباشتن روش پشتیبانی نمی کند ، هر تابع باید اسم خاص خودش را داشته باشد ، حتی اگر هر سه تابع یک وظیفه واحد را انجام دهند. از نظر ذهنی این حالت ، شرایط پیچیده تری را نسبت به آنچه واقعا" وجود دارد ، ایجاب می کند . اگرچه مفهوم اصلی این توابع یکسـان اسـت ، امـا همچنان مجبورید سه اسم را بخاطر بسپارید . این شرایط در جاوا اتفاق نمی افتد ، زیرا تمامی روش های مربوط به قدر مطلق می توانند از یک اسم واحد استفاده نمایند . در حقیقت کتابخانه کلاس استاندارد جاوا (class library standard s'Java ) شامل یک روش قدر مطلق موسوم به abs ()می باشـد . ایـن روش توسـط کـلاس Math در جـاوا انباشـته شـده تـا کلیـه انـواع رقمـی را مـدیریت نمایـد . جـاوا بـر اساس نوع آرگومان ، تصمیم می گیرد که کدام روایت از abs ()را فراخوانی نماید . ارزش انباشتن روشـها در ایـن اسـت کـه مـی تـوان بـا استفاده از یک اسم مشترک به کلیه روشهای مرتبط با هم دسترسی پیدا کرد. بدین ترتیب ،اسـم abs معـرف عمـل عمـومی اسـت کـه اجـرا خواهد شد . تعیین روایت مخصوص برای هر یک از شرایط خاص بر عهده کامپایلرمی باشد . برنامه نویس فقط کافی است تـا اعمـال عمـومی که باید انجام شوند را بخاطربسپارد . بدین ترتیب با استفاده از مفهوم چند شکلی ، چندین اسم به یک اسم خلاصه شده اند. اگرچـه ایـن مثـال بسیارساده بود، امااگر مفهوم زیربنایی آن را گسترش دهید ، می فهمید که انباشتن روشها تا چه حد در مدیریت پیچیدگی در برنامه ها سـودمند و کارساز است .  

 

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

 

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

سؤالات خود در رابطه با این مقاله و همچنین انتقادات و پیشنهادات خود را در قسمت نظرات با ما در میان بگذار


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