JavaRush /مدونة جافا /Random-AR /الاستثناءات في Java: الالتقاط والتعامل

الاستثناءات في Java: الالتقاط والتعامل

نشرت في المجموعة
مرحبًا! أكره أن أخبرك بذلك، لكن جزءًا كبيرًا من عمل المبرمج هو التعامل مع الأخطاء. وفي أغلب الأحيان - بمفردهم. يحدث أنه لا يوجد أشخاص لا يرتكبون الأخطاء. ولا توجد مثل هذه البرامج أيضًا. بالطبع، الشيء الرئيسي عند العمل على الخطأ هو فهم سببه. ويمكن أن يكون هناك مجموعة كاملة من الأسباب لذلك في البرنامج. في مرحلة ما، واجه منشئو Java سؤالاً: ما يجب القيام به مع هذه الأخطاء المحتملة جدًا في البرامج؟ تجنبهم تماما أمر غير واقعي. يمكن للمبرمجين أن يكتبوا شيئًا من المستحيل حتى تخيله :) وهذا يعني أنه من الضروري أن نبني في اللغة آلية للتعامل مع الأخطاء. بمعنى آخر، إذا حدث خطأ ما في البرنامج، فستكون هناك حاجة إلى برنامج نصي لمزيد من العمل. ما الذي يجب أن يفعله البرنامج بالضبط عند حدوث خطأ؟ اليوم سوف نتعرف على هذه الآلية. ويسمى "الاستثناءات " .

ما هو الاستثناء في جافا

الاستثناء هو بعض المواقف الاستثنائية غير المخطط لها التي حدثت أثناء تشغيل البرنامج. يمكن أن يكون هناك العديد من الأمثلة على الاستثناءات في Java. على سبيل المثال، قمت بكتابة تعليمات برمجية تقرأ النص من ملف وتعرض السطر الأول إلى وحدة التحكم.
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 (Системе не удается найти указанный путь)
يتم تمثيل كل استثناء بفئة منفصلة في Java. جميع فئات الاستثناء تأتي من "سلف" مشترك - الفئة الأصل Throwable. عادةً ما يعكس اسم فئة الاستثناء بشكل مختصر سبب حدوثه:
  • FileNotFoundException(لم يتم العثور على الملف)
  • ArithmeticException(استثناء عند إجراء عملية حسابية)
  • ArrayIndexOutOfBoundsException(يتم تحديد عدد خلية الصفيف بما يتجاوز طولها). على سبيل المثال، إذا حاولت إخراج مصفوفة خلايا[23] إلى وحدة التحكم لمصفوفة طولها 10.
يوجد ما يقرب من 400 فئة من هذا القبيل في Java! لماذا هذا العدد الكبير؟ على وجه التحديد لجعل الأمر أكثر ملاءمة للمبرمجين للعمل معهم. تخيل أنك كتبت برنامجًا، وعندما يتم تشغيله، فإنه يطرح استثناءً يبدو كالتالي:
Exception in thread "main"
اه اه :/ لا يوجد شيء واضح. ما هو نوع الخطأ ومن أين جاء غير واضح. لا توجد معلومات مفيدة. ولكن بفضل مجموعة متنوعة من الفئات، يحصل المبرمج على الشيء الرئيسي لنفسه - نوع الخطأ وسببه المحتمل، الموجود في اسم الفصل. بعد كل شيء، إنه شيء مختلف تمامًا أن تراه في وحدة التحكم:
Exception in thread "main" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (Системе не удается найти указанный путь)
يصبح من الواضح على الفور ما هي المشكلة و"في أي اتجاه يجب الحفر" لحل المشكلة! الاستثناءات، مثل أي مثيلات للفئات، هي كائنات.

اصطياد ومعالجة الاستثناءات

للعمل مع الاستثناءات في Java، هناك كتل تعليمات برمجية خاصة: tryو catchو finally. الاستثناءات: الاعتراض والمعالجة - 2يتم وضع الكود الذي يتوقع المبرمج حدوث استثناءات فيه في كتلة try. هذا لا يعني أن الاستثناء سيحدث بالضرورة في هذا الموقع. وهذا يعني أنه يمكن أن يحدث هناك، والمبرمج على علم بذلك. يتم وضع نوع الخطأ الذي تتوقع تلقيه في كتلة 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!");
   }
}
خاتمة:

Ошибка! Файл не найден!
نضع الكود الخاص بنا في كتلتين. في الكتلة الأولى نتوقع حدوث خطأ "لم يتم العثور على الملف". هذه كتلة 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 أشياء. أولاً. بمجرد حدوث استثناء في أي سطر من التعليمات البرمجية في كتلة المحاولة، لن يتم تنفيذ التعليمات البرمجية بعد ذلك. سوف "ينتقل" تنفيذ البرنامج فورًا إلى الكتلة 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. tryبعد ذلك، لن يتم تنفيذ الأسطر من 6 إلى 10 من الكتلة . كما قلنا، بدأ البرنامج على الفور في تنفيذ الكتلة 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 نوعًا مختلفًا من الأخطاء :) ثالثًا. كيف تعرف ما هي الاستثناءات التي قد يطرحها الكود الخاص بك؟ حسنًا، يمكنك بالطبع تخمين البعض، لكن من المستحيل الاحتفاظ بكل شيء في رأسك. لذلك، يعرف مترجم Java الاستثناءات الأكثر شيوعًا ويعرف المواقف التي يمكن أن تحدث فيها. على سبيل المثال، إذا كتبت تعليمات برمجية وكان المترجم يعلم أنه قد يحدث نوعان من الاستثناءات أثناء تشغيله، فلن يتم تجميع التعليمات البرمجية الخاصة بك حتى تتعامل معهم. وسنرى أمثلة على ذلك أدناه. الآن فيما يتعلق بمعالجة الاستثناءات. هناك طريقتان لمعالجتها. لقد التقينا بالفعل بالأول - يمكن للطريقة التعامل مع الاستثناء بشكل مستقل في الكتلة 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في هذه العملية ، يمكن أن يحدث خطأ أثناء الإدخال والإخراج (الإدخال والإخراج) بسهولة . الآن يخبرك المترجم، "يا صديقي، لن أوافق على هذا الكود أو أقوم بتجميعه حتى تخبرني بما يجب أن أفعله في حالة حدوث أحد هذه الاستثناءات. ويمكن أن يحدث ذلك بالتأكيد بناءً على الكود الذي كتبته! . ليس هناك مكان للذهاب إليه، تحتاج إلى معالجة كليهما! خيار المعالجة الأول مألوف لنا بالفعل: نحتاج إلى وضع الكود الخاص بنا في كتلة 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()تواجه الآن نفس الخيارين: يجب عليها إما معالجة كلا الاستثناءين اللذين "طارا" إليها باستخدام 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