CodeGym /مدونة جافا /Random-AR /معالجة الاستثناءات في جافا
John Squirrels
مستوى
San Francisco

معالجة الاستثناءات في جافا

نشرت في المجموعة
أهلاً! اليوم سوف نلقي نظرة فاحصة على معالجة الاستثناءات في جافا. أكره أن أذكر ذلك، لكن جزءًا كبيرًا من عمل المبرمج هو التعامل مع الأخطاء. في أغلب الأحيان، خاصة به. اتضح أنه لا يوجد أشخاص لا يخطئون. ولا توجد مثل هذه البرامج أيضًا. بالطبع، عند التعامل مع الخطأ، فإن الشيء الرئيسي هو فهم سببه. والكثير من الأشياء يمكن أن تسبب أخطاء في البرنامج. في مرحلة ما، سأل منشئو 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. الإخراج: استثناء في مؤشر الترابط "الرئيسي" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (لا يمكن للنظام العثور على المسار المحدد) في Java، يتم تمثيل كل استثناء بواسطة فئة منفصلة. كل فئات الاستثناء هذه مستمدة من "سلف" مشترك - Throwableالفئة الأصل. عادةً ما يعكس اسم فئة الاستثناء بشكل موجز سبب حدوث الاستثناء:
  • FileNotFoundException(لم يتم العثور على الملف)

  • ArithmeticException(حدث استثناء أثناء إجراء عملية حسابية)

  • ArrayIndexOutOfBoundsException(الفهرس خارج حدود المصفوفة). على سبيل المثال، يحدث هذا الاستثناء إذا حاولت عرض الموضع 23 من صفيف يحتوي على 10 عناصر فقط.
إجمالاً، تحتوي Java على ما يقرب من 400 فئة من هذا القبيل! لماذا هذا العدد الكبير؟ لجعلها أكثر ملاءمة للمبرمجين للعمل معها. تخيل هذا: تكتب برنامجًا، وأثناء تشغيله يولد استثناءً يبدو كالتالي:
Exception in thread "main"
اههه. :/ هذا لا يساعد كثيرا. ليس من الواضح ماذا يعني الخطأ أو من أين جاء. لا توجد معلومات مفيدة هنا. لكن التنوع الكبير في فئات الاستثناء في Java يمنح المبرمج ما يهم أكثر: نوع الخطأ وسببه المحتمل (المضمن في اسم الفئة). إنه شيء آخر يجب رؤيته
Exception in thread "main" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (The system cannot find the specified path)
من الواضح على الفور ما هي المشكلة وأين نبدأ في الحفر لحل المشكلة! الاستثناءات، مثل مثيلات أي فئة، هي كائنات.

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

تحتوي Java على كتل خاصة من التعليمات البرمجية للتعامل مع الاستثناءات: tryو catch. يتم وضع الكود الذي يعتقد المبرمج أنه قد يحدث فيه استثناء في الكتلة. هذا لا يعني أنه سيحدث استثناء هنا. يعني أنه قد يحدث هنا، والمبرمج على علم بهذا الاحتمال. يتم وضع نوع الخطأ الذي تتوقع حدوثه في الكتلة . يحتوي هذا أيضًا على كافة التعليمات البرمجية التي يجب تنفيذها في حالة حدوث استثناء. هنا مثال: finallyالاستثناءات: الإمساك والتعامل - 2trycatch
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، فلن FileNotFoundExceptionيتم اكتشافها.
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!");
   }
}
الإخراج: استثناء في مؤشر الترابط "الرئيسي" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (يتعذر على النظام العثور على المسار المحدد) لم يتمcatch تشغيل الكود الموجود في الكتلة، لأننا "قمنا بتكوين" تم التقاط هذه الكتلة ArithmeticException، وألقى الكود الموجود في tryالكتلة نوعًا مختلفًا: FileNotFoundException. لم نكتب أي تعليمات برمجية للتعامل معها FileNotFoundException، لذلك يعرض البرنامج المعلومات الافتراضية لـ FileNotFoundException. هنا عليك الانتباه إلى ثلاثة أشياء. رقم واحد. بمجرد حدوث استثناء على سطر ما في tryالكتلة، لن يتم تنفيذ التعليمات البرمجية التالية. تنفيذ البرنامج على الفور "يقفز" إلى catchالكتلة. على سبيل المثال:
public static void main(String[] args) {
   try {
       System.out.println("Divide 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("will not");
       System.out.println("be");
       System.out.println("executed!");

   } catch (ArithmeticException e) {

       System.out.println("The program jumped to the catch block!");
       System.out.println("Error! You can't divide by zero!");
   }
}
الإخراج: القسمة على صفر قفز البرنامج إلى كتلة الالتقاط! خطأ! لا يمكنك القسمة على صفر! في السطر الثاني من tryالكتلة، نحاول القسمة على 0، مما يؤدي إلى ArithmeticException. وبالتالي، tryلن يتم تنفيذ الأسطر من 3 إلى 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. إذا FileNotFoundExceptionحدث في الكتلة، فسيتم تنفيذ الكتلة tryالأولى . catchفي حالة ArithmeticExceptionحدوث ذلك، سيتم تنفيذ الكتلة الثانية. يمكنك كتابة 50 catchقطعة إذا أردت ذلك. بالطبع، من الأفضل عدم كتابة تعليمات برمجية يمكنها طرح 50 نوعًا مختلفًا من الاستثناءات. :) ثالث. كيف تعرف الاستثناءات التي قد يطرحها الكود الخاص بك؟ حسنًا، قد تكون قادرًا على تخمين بعضها، لكن من المستحيل أن تبقي كل شيء في رأسك. لذلك يعرف مترجم Java الاستثناءات الأكثر شيوعًا والمواقف التي قد تحدث فيها. على سبيل المثال، إذا كتبت تعليمات برمجية يعرف المترجم أنها قد تؤدي إلى نوعين من الاستثناءات، فلن يتم تجميع التعليمات البرمجية الخاصة بك حتى تتعامل معها. وسنرى أمثلة على ذلك أدناه.

الآن بعض الكلمات حول معالجة الاستثناءات

هناك طريقتان للتعامل مع الاستثناءات في 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("File input/output error!");
       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 then calls your printFirstString() method with the file it needs
   printFirstString("C:\\Users\\Henry\\Desktop\\testFile.txt");
}
نحصل على خطأ! لن يتم تجميع هذا الرمز! لم نكتب كود معالجة الاستثناءات في printFirstString()الطريقة. ونتيجة لذلك، تقع هذه المهمة الآن على عاتق أولئك الذين يستخدمون هذه الطريقة. بمعنى آخر، methodWrittenByYourColleague()لدى الطريقة الآن نفس الخيارين: يجب عليها إما استخدام كتلة try-catchللتعامل مع كلا الاستثناءين، أو إعادة طرحهما.
public static void yourColleagueMethod() throws FileNotFoundException, IOException {
   // The method does something

   //...and then calls your printFirstString() method with the file it needs
   printFirstString("C:\\Users\\Henry\\Desktop\\testFile.txt");
}
في الحالة الثانية، الطريقة التالية في مكدس الاستدعاءات - الطريقة التي تستدعي methodWrittenByYourColleague()- يجب أن تتعامل مع الاستثناءات. لهذا السبب نسمي هذا "رمي الاستثناء أو تمريره". إذا قمت بطرح استثناءات لأعلى باستخدام الكلمة الأساسية throws، فسيتم تجميع التعليمات البرمجية الخاصة بك. في هذه المرحلة، يبدو أن المترجم يقول: "حسنًا، حسنًا. تحتوي شفرتك على مجموعة من الاستثناءات المحتملة، لكنني سأقوم بتجميعها. لكننا سنعود إلى هذه المحادثة!" وعندما تستدعي أي عملية تحتوي على استثناءات لم تتم معالجتها، فإن المترجم يفي بوعده ويذكرك بها مرة أخرى. أخيرًا، سنتحدث عن finallyالحظر (آسف على التورية). هذا هو الجزء الأخير من 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's the finally block!");
   }
}
finallyفي هذا المثال، سيتم تنفيذ التعليمات البرمجية الموجودة داخل الكتلة في كلتا الحالتين. إذا تم تشغيل الكود الموجود في tryالكتلة بالكامل دون طرح أي استثناءات، finallyفسيتم تشغيل الكتلة في النهاية. إذا تمت مقاطعة التعليمات البرمجية الموجودة داخل tryالكتلة بواسطة استثناء وانتقل البرنامج إلى catchالكتلة، finallyفستظل الكتلة تعمل بعد التعليمات البرمجية الموجودة داخل catchالكتلة.

لماذا هذا ضروري؟

والغرض الرئيسي منه هو تنفيذ التعليمات البرمجية الإلزامية: التعليمات البرمجية التي يجب تنفيذها بغض النظر عن الظروف. على سبيل المثال، غالبًا ما يحرر بعض الموارد التي يستخدمها البرنامج. في الكود الخاص بنا، نفتح دفقًا لقراءة المعلومات من الملف وتمريرها إلى الكائن BufferedReader. يجب علينا إغلاق القارئ لدينا وتحرير الموارد. يجب أن يتم ذلك مهما حدث، عندما يعمل البرنامج كما ينبغي، وعندما يُحدث استثناءً. تعد الكتلة 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's the finally block!");
       if (reader != null) {
           reader.close();
       }
   }
}
الآن نحن على يقين من أننا سوف نعتني بالموارد، بغض النظر عما يحدث عند تشغيل البرنامج. :) هذا ليس كل ما تحتاج لمعرفته حول الاستثناءات. تعد معالجة الأخطاء موضوعًا مهمًا للغاية في البرمجة. يتم تخصيص الكثير من المقالات لذلك. في الدرس التالي، سنكتشف أنواع الاستثناءات الموجودة وكيفية إنشاء الاستثناءات الخاصة بك. :) اراك لاحقا!
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION