JavaRush /وبلاگ جاوا /Random-FA /استثناها در جاوا: گرفتن و دست زدن

استثناها در جاوا: گرفتن و دست زدن

در گروه منتشر شد
سلام! من از شکستن آن برای شما متنفرم، اما بخش بزرگی از کار یک برنامه نویس مقابله با خطاها است. و اغلب - با خود. اتفاقاً هیچ آدمی نیست که اشتباه نکند. و چنین برنامه هایی نیز وجود ندارد. البته نکته اصلی هنگام کار بر روی یک خطا، درک علت آن است. و می تواند یک دسته کلی از دلایل برای چنین چیزی در برنامه وجود داشته باشد. در یک نقطه، سازندگان جاوا با این سوال مواجه شدند: با این خطاهای بسیار بالقوه در برنامه ها چه باید کرد؟ اجتناب از آنها به طور کامل غیر واقعی است. برنامه نویسان می توانند چیزی بنویسند که حتی تصورش غیرممکن است :) این بدان معنی است که باید مکانیزمی برای مقابله با خطاها در زبان ایجاد کرد. به عبارت دیگر، اگر خطایی در برنامه رخ داده باشد، برای کار بیشتر به یک اسکریپت نیاز است. در صورت بروز خطا، برنامه دقیقاً چه کاری باید انجام دهد؟ امروز با این مکانیسم آشنا می شویم. و به آن «استثناها » می گویند .

استثنا در جاوا چیست

یک استثنا برخی از موقعیت های استثنایی و برنامه ریزی نشده است که در حین اجرای برنامه رخ داده است. در جاوا می توان نمونه های زیادی از استثناها وجود داشت. به عنوان مثال، شما کدی نوشتید که متنی را از یک فایل می خواند و خط اول را به کنسول نمایش می دهد.
public class Main {

   public static void main(String[] args) throws IOException {
       BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));
       String firstString = reader.readLine();
       System.out.println(firstString);
   }
}
اما چنین فایلی وجود ندارد! نتیجه برنامه یک استثنا خواهد بود - FileNotFoundException. نتیجه:

Exception in thread "main" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (Системе не удается найти указанный путь)
هر استثنا با یک کلاس جداگانه در جاوا نشان داده می شود. همه کلاس های استثنا از یک "جد" مشترک - کلاس والد می آیند Throwable. نام کلاس استثنا معمولاً به طور خلاصه دلیل وقوع آن را نشان می دهد:
  • FileNotFoundException(فایل پیدا نشد)
  • ArithmeticException(به استثنای انجام یک عملیات ریاضی)
  • ArrayIndexOutOfBoundsException(تعداد سلول آرایه بیش از طول آن مشخص شده است). برای مثال، اگر سعی کنید آرایه سلولی[23] را برای یک آرایه به طول 10 به کنسول خروجی دهید.
تقریبا 400 کلاس از این قبیل در جاوا وجود دارد! چرا اینقدر زیاد؟ دقیقاً برای اینکه برنامه نویسان کار با آنها را راحت تر کنند. تصور کنید: شما یک برنامه نوشتید، و وقتی اجرا می شود، یک استثنا ایجاد می کند که شبیه این است:
Exception in thread "main"
اوه اوه:/ هیچ چیز مشخص نیست. چه نوع خطایی است و از کجا آمده است، مشخص نیست. هیچ اطلاعات مفیدی وجود ندارد. اما به لطف چنین کلاس های متنوعی، برنامه نویس چیز اصلی را برای خود به دست می آورد - نوع خطا و علت احتمالی آن، که در نام کلاس موجود است. از این گذشته، دیدن در کنسول یک چیز کاملاً متفاوت است:
Exception in thread "main" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (Системе не удается найти указанный путь)
بلافاصله مشخص می شود که مشکل ممکن است چیست و "در کدام جهت حفاری" برای حل مشکل! استثناها، مانند هر نمونه ای از کلاس ها، اشیا هستند.

موارد استثناء گرفتن و جابجایی

برای کار با استثناها در جاوا، بلوک های کد خاصی وجود دارد try: catchو finally. استثناها: رهگیری و پردازش - 2کدی که برنامه نویس انتظار دارد استثناهایی در آن رخ دهد در یک بلوک قرار می گیرد try. این بدان معنا نیست که لزوماً یک استثنا در این مکان رخ خواهد داد. این بدان معنی است که می تواند در آنجا اتفاق بیفتد و برنامه نویس از آن آگاه است. نوع خطایی که انتظار دریافت آن را دارید در یک بلوک catch("catch") قرار می گیرد. در اینجا نیز تمام کدهایی که در صورت بروز استثنا باید اجرا شوند، قرار می گیرند. در اینجا یک مثال است:
public static void main(String[] args) throws IOException {
   try {
       BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));

       String firstString = reader.readLine();
       System.out.println(firstString);
   } catch (FileNotFoundException e) {

       System.out.println("Error! File not found!");
   }
}
نتیجه:

Ошибка! Файл не найден!
کد خود را در دو بلوک قرار می دهیم. در بلوک اول انتظار داریم که خطای "File not found" رخ دهد. این یک بلوک است try. در مرحله دوم به برنامه می گوییم که در صورت بروز خطا چه کاری انجام دهد. علاوه بر این، نوع خاصی از خطا وجود دارد - FileNotFoundException. اگر یک کلاس استثنای دیگر را به براکت های بلوک منتقل کنیم catch، آن را نمی گیرد.
public static void main(String[] args) throws IOException {
   try {
       BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));
       String firstString = reader.readLine();
       System.out.println(firstString);
   } catch (ArithmeticException e) {

       System.out.println("Error! File not found!");
   }
}
نتیجه:

Exception in thread "main" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (Системе не удается найти указанный путь)
کد موجود در بلوک catchکار نمی کند زیرا ما این بلوک را برای رهگیری "پیکربندی" کردیم ArithmeticExceptionو کد موجود در بلوک tryنوع دیگری را بیرون انداخت - FileNotFoundException. ما اسکریپتی برای آن ننوشتیم FileNotFoundException، بنابراین برنامه اطلاعاتی را که به طور پیش فرض برای نمایش داده می شود در کنسول نمایش می دهد FileNotFoundException. در اینجا باید به 3 مورد توجه کنید. اولین. به محض اینکه یک استثنا در هر خط کد در یک بلوک try رخ دهد، کد پس از آن دیگر اجرا نخواهد شد. اجرای برنامه بلافاصله به بلوک "پرش" می کند catch. مثلا:
public static void main(String[] args) {
   try {
       System.out.println("Divide a number by zero");
       System.out.println(366/0);//this line of code will throw an exception

       System.out.println("This");
       System.out.println("code");
       System.out.println("Not");
       System.out.println("will");
       System.out.println("done!");

   } catch (ArithmeticException e) {

       System.out.println("The program jumped to the catch block!");
       System.out.println("Error! You can't divide by zero!");
   }
}
نتیجه:

Делим число на ноль 
Программа перепрыгнула в блок catch! 
Ошибка! Нельзя делить на ноль! 
در بلوک tryخط دوم، سعی کردیم یک عدد را بر 0 تقسیم کنیم که منجر به یک استثنا شد ArithmeticException. پس از این، خطوط 6-10 بلوک tryدیگر اجرا نمی شوند. همانطور که گفتیم، برنامه بلافاصله شروع به اجرای بلوک کرد catch. دومین. می تواند چندین بلوک وجود داشته باشد catch. اگر کد موجود در یک بلوک tryمی تواند نه یک، بلکه چندین نوع استثنا ایجاد کند، می توانید بلوک خود را برای هر یک از آنها بنویسید catch.
public static void main(String[] args) throws IOException {
   try {
       BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));

       System.out.println(366/0);
       String firstString = reader.readLine();
       System.out.println(firstString);
   } catch (FileNotFoundException e) {

       System.out.println("Error! File not found!");

   } catch (ArithmeticException e) {

       System.out.println("Error! Division by 0!");

   }
}
در این مثال ما دو بلوک نوشتیم catch. اگر، در بلوک tryرخ دهد FileNotFoundException، اولین بلوک اجرا خواهد شد catch. اگر اتفاق بیفتد ArithmeticException، دومی اجرا می شود. شما می توانید حداقل 50 بلوک بنویسید catch. اما، البته، بهتر است کدی ننویسید که بتواند 50 نوع خطا ایجاد کند :) سوم. چگونه می دانید کد شما ممکن است چه استثناهایی ایجاد کند؟ خوب، مطمئناً می توانید در مورد برخی حدس بزنید، اما غیرممکن است که همه چیز را در ذهن خود نگه دارید. بنابراین، کامپایلر جاوا در مورد رایج ترین استثناها می داند و می داند که در چه موقعیت هایی می توانند رخ دهند. به عنوان مثال، اگر شما کد نوشتید و کامپایلر بداند که 2 نوع استثنا ممکن است در حین کار رخ دهد، کد شما تا زمانی که آنها را مدیریت نکنید، کامپایل نخواهد شد. نمونه هایی از این را در ادامه خواهیم دید. در حال حاضر در مورد رسیدگی به استثنا. 2 راه برای پردازش آنها وجود دارد. ما قبلاً اولین مورد را ملاقات کرده ایم - متد می تواند استثنا را به طور مستقل در بلوک مدیریت کند catch(). یک گزینه دوم وجود دارد - این روش می تواند یک استثنا را در پشته تماس ایجاد کند. چه مفهومی داره؟ به عنوان مثال، در کلاس ما یک متد داریم - همان printFirstString()- که یک فایل را می خواند و اولین خط آن را به کنسول نمایش می دهد:
public static void printFirstString(String filePath) {

   BufferedReader reader = new BufferedReader(new FileReader(filePath));
   String firstString = reader.readLine();
   System.out.println(firstString);
}
در حال حاضر کد ما کامپایل نمی شود زیرا دارای استثنائات کنترل نشده است. در خط 1 مسیر فایل را نشان می دهید. کامپایلر می داند که چنین کدی به راحتی می تواند منجر به FileNotFoundException. در خط 3 متن فایل را می خوانید. IOExceptionدر این فرآیند ، یک خطا در حین ورودی-خروجی (Input-Output) به راحتی می تواند رخ دهد . حالا کامپایلر به شما می‌گوید: «رفیق، من این کد را تأیید نمی‌کنم یا آن را کامپایل نمی‌کنم تا زمانی که به من بگویید اگر یکی از این استثناها رخ داد، چه کاری باید انجام دهم. و قطعاً می توانند بر اساس کدی که شما نوشته اید اتفاق بیفتند!» . جایی برای رفتن وجود ندارد، شما باید هر دو را پردازش کنید! اولین گزینه پردازش قبلاً برای ما آشنا است: باید کد خود را در یک بلوک قرار دهیم tryو دو بلوک اضافه کنیم catch:
public static void printFirstString(String filePath) {

   try {
       BufferedReader reader = new BufferedReader(new FileReader(filePath));
       String firstString = reader.readLine();
       System.out.println(firstString);
   } catch (FileNotFoundException e) {
       System.out.println("Error, file not found!");
       e.printStackTrace();
   } catch (IOException e) {
       System.out.println("Error while inputting/outputting data from file!");
       e.printStackTrace();
   }
}
اما این تنها گزینه نیست. می‌توانیم از نوشتن یک اسکریپت برای خطای داخل متد اجتناب کنیم و به سادگی استثنا را به بالا بیاندازیم. این کار با استفاده از کلمه کلیدی throwsکه در اعلان متد نوشته شده است انجام می شود:
public static void printFirstString(String filePath) throws FileNotFoundException, IOException {
   BufferedReader reader = new BufferedReader(new FileReader(filePath));
   String firstString = reader.readLine();
   System.out.println(firstString);
}
بعد از کلمه، throwsهمه انواع استثناهایی را که این روش می تواند در حین کار پرتاب کند، با کاما از هم جدا می کنیم. چرا این کار انجام می شود؟ حال، اگر شخصی در برنامه بخواهد متد را فراخوانی کند printFirstString()، باید خودش مدیریت استثنا را پیاده سازی کند. به عنوان مثال، در قسمت دیگری از برنامه، یکی از همکاران شما متدی نوشت که در آن متد شما را فراخوانی می کند printFirstString():
public static void yourColleagueMethod() {

   //...your colleague's method does something

   //...and at one moment calls your printFirstString() method with the file it needs
   printFirstString("C:\\Users\\Eugene\\Desktop\\testFile.txt");
}
خطا، کد کامپایل نمی شود! ما اسکریپت رسیدگی به خطا را در متد printFirstString()ننوشتیم . بنابراین، وظیفه بر دوش کسانی است که از این روش استفاده خواهند کرد. یعنی، روش yourColleagueMethod()اکنون با همان 2 گزینه روبرو است: یا باید هر دو استثنا را که با استفاده از "به آن می رسند" پردازش کند try-catchیا آنها را بیشتر ارسال کند.
public static void yourColleagueMethod() throws FileNotFoundException, IOException {
   //...the method does something

   //...and at one moment calls your printFirstString() method with the file it needs
   printFirstString("C:\\Users\\Eugene\\Desktop\\testFile.txt");
}
در حالت دوم، پردازش بر روی شانه روش بعدی در پشته قرار می گیرد - روشی که فراخوانی می کند yourColleagueMethod(). به همین دلیل است که چنین مکانیزمی "پرتاب یک استثنا به سمت بالا" یا "عبور به بالا" نامیده می شود. هنگامی که با استفاده از استثناها را ایجاد می کنید throws، کد کامپایل می شود. در این لحظه، به نظر می رسد که کامپایلر می گوید: «باشه، باشه. کد شما شامل یک سری استثناهای بالقوه است، اما به هر حال آن را کامپایل می کنم. ما به این گفتگو باز خواهیم گشت!» و وقتی متدی را در جایی از برنامه فراخوانی می کنید که استثناهای آن را مدیریت نکرده است، کامپایلر به قول خود عمل می کند و دوباره آنها را به شما یادآوری می کند. در نهایت، ما در مورد بلوک finally(ببخشید جناس) صحبت می کنیم. این آخرین بخش از triumvirate رسیدگی به استثنا است try-catch-finally. ویژگی آن این است که تحت هر سناریوی عملیاتی برنامه اجرا می شود.
public static void main(String[] args) throws IOException {
   try {
       BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));

       String firstString = reader.readLine();
       System.out.println(firstString);
   } catch (FileNotFoundException e) {
       System.out.println("Error! File not found!");
       e.printStackTrace();
   } finally {
       System.out.println("And here is the finally block!");
   }
}
در این مثال کد داخل بلوک finallyدر هر دو حالت اجرا می شود. اگر کد موجود در بلوک tryبه طور کامل اجرا شود و استثنایی ایجاد نکند، بلوک در پایان فعال می شود finally. اگر کد داخل tryقطع شود و برنامه به بلوک بپرد catch، پس از اجرای کد داخل catch، بلاک همچنان انتخاب خواهد شد finally. چرا نیاز است؟ هدف اصلی آن اجرای قسمت مورد نیاز کد است. قسمتی که بدون توجه به شرایط باید تکمیل شود. به عنوان مثال، اغلب برخی از منابع مورد استفاده برنامه را آزاد می کند. در کد خود، جریانی را برای خواندن اطلاعات از یک فایل باز می کنیم و آن را به یک فایل ارسال می کنیم BufferedReader. مال ما readerباید بسته شود و منابع آزاد شود. در هر صورت باید این کار انجام شود: مهم نیست که برنامه همانطور که انتظار می رود کار می کند یا استثناء ایجاد می کند. انجام این کار در یک بلوک راحت است finally:
public static void main(String[] args) throws IOException {

   BufferedReader reader = null;
   try {
       reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));

       String firstString = reader.readLine();
       System.out.println(firstString);
   } catch (FileNotFoundException e) {
       e.printStackTrace();
   } finally {
       System.out.println("And here is the finally block!");
       if (reader != null) {
           reader.close();
       }
   }
}
اکنون ما کاملاً مطمئن هستیم که از منابع اشغال شده مراقبت کرده ایم، صرف نظر از اینکه در حین اجرای برنامه چه اتفاقی می افتد :) این تمام چیزی نیست که باید در مورد استثناها بدانید. مدیریت خطا یک موضوع بسیار مهم در برنامه نویسی است: بیش از یک مقاله به آن اختصاص داده شده است. در درس بعدی خواهیم آموخت که چه نوع استثنایی وجود دارد و چگونه استثنای خود را ایجاد کنید :) آنجا شما را می بینیم!
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION