معالجة الأخطاء في Java: try و catch و throw
المستخدم يكتب حروفاً مكان الرقم، والملف المطلوب غير موجود، والقسمة على صفر تحدث فجأة. البرنامج الجيد لا ينهار أمام هذه المواقف، بل يتوقعها ويتعامل معها. في هذا الدرس ستتعلم نظام الاستثناءات (Exceptions) في Java: كيف تلتقط الخطأ، وكيف ترميه بنفسك، ولماذا يجبرك المترجم أحياناً على معالجته.
ما هو الاستثناء؟
Section titled “ما هو الاستثناء؟”عندما يحدث خطأ وقت التشغيل، تنشئ Java كائناً يصف المشكلة و«ترميه». إذا لم يلتقطه أحد، يتوقف البرنامج ويطبع رسالة مثل:
Exception in thread "main" java.lang.ArithmeticException: / by zero at Main.main(Main.java:5)اقرأ الرسالة بهدوء، ففيها كل ما تحتاج: نوع الخطأ (ArithmeticException)، ووصفه (/ by zero)، ورقم السطر الذي حدث فيه.
try و catch
Section titled “try و catch”ضع الكود الخطِر داخل try، واكتب خطة الطوارئ داخل catch:
try { int result = 10 / 0; System.out.println("لن يُطبع هذا السطر");} catch (ArithmeticException e) { System.out.println("خطأ حسابي: " + e.getMessage());}System.out.println("البرنامج مستمر");خطأ حسابي: / by zeroالبرنامج مستمرعند حدوث الخطأ يقفز التنفيذ فوراً إلى catch، وتُهمل بقية أسطر try. بعدها يكمل البرنامج طريقه بشكل طبيعي. والمتغير e هو كائن الاستثناء، و getMessage() تُرجع وصف المشكلة.
الأمر مثل شبكة الأمان تحت لاعب السيرك: إذا سقط، التقطته الشبكة واستمر العرض.
أكثر من catch
Section titled “أكثر من catch”يمكنك التعامل مع كل نوع خطأ بطريقة مختلفة:
String[] inputs = { "25", "abc", null };for (String input : inputs) { try { int number = Integer.parseInt(input.trim()); System.out.println("العدد: " + number); } catch (NumberFormatException e) { System.out.println("ليس رقماً: " + input); } catch (NullPointerException e) { System.out.println("القيمة فارغة null"); }}العدد: 25ليس رقماً: abcالقيمة فارغة nullJava تجرّب أقسام catch بالترتيب وتنفّذ أول قسم يطابق نوع الخطأ. هذه بعض الأنواع التي ستقابلها كثيراً:
| الاستثناء | متى يحدث |
|---|---|
NullPointerException |
استدعاء دالة على متغير قيمته null |
ArrayIndexOutOfBoundsException |
رقم عنصر خارج حدود المصفوفة |
NumberFormatException |
تحويل نص ليس رقماً إلى عدد |
ArithmeticException |
قسمة عدد صحيح على صفر |
finally: يُنفَّذ دائماً
Section titled “finally: يُنفَّذ دائماً”قسم finally يعمل في كل الأحوال: سواء نجح الكود أو فشل. نستخدمه لأعمال التنظيف مثل إغلاق ملف أو اتصال:
int[] data = { 1, 2, 3 };try { System.out.println(data[5]);} catch (ArrayIndexOutOfBoundsException e) { System.out.println("موضع غير موجود");} finally { System.out.println("انتهت المحاولة");}موضع غير موجودانتهت المحاولةthrow: ارمِ استثناءك
Section titled “throw: ارمِ استثناءك”أنت أيضاً تستطيع رمي استثناء عندما تستقبل دالتك قيمة غير منطقية:
static void setAge(int age) { if (age < 0) { throw new IllegalArgumentException("العمر لا يمكن أن يكون سالباً: " + age); } System.out.println("تم تسجيل العمر " + age);}try { setAge(15); setAge(-4); setAge(20);} catch (IllegalArgumentException e) { System.out.println("مرفوض: " + e.getMessage());}تم تسجيل العمر 15مرفوض: العمر لا يمكن أن يكون سالباً: -4throw توقف الدالة فوراً وترسل الخطأ إلى من استدعاها. لاحظ أن setAge(20) لم تُنفَّذ، لأن التنفيذ قفز إلى catch عند أول خطأ.
المفحوصة وغير المفحوصة
Section titled “المفحوصة وغير المفحوصة”الاستثناءات في Java عائلتان:
- غير المفحوصة (Unchecked): ترث من
RuntimeException، مثل كل ما رأيته حتى الآن. سببها غالباً خطأ في الكود نفسه، ولا يجبرك المترجم على معالجتها. - المفحوصة (Checked): مثل
IOExceptionعند قراءة الملفات. سببها ظروف خارج سيطرتك، ولذلك يرفض المترجم الكود إذا تجاهلتها.
أمامك مع النوع المفحوص حلّان: إما try/catch، أو الإعلان بكلمة throws أن دالتك قد ترمي هذا الخطأ فيتحمل المسؤولية من يستدعيها:
static String firstLine(String path) throws IOException { return Files.readAllLines(Path.of(path)).get(0);}try { System.out.println(firstLine("missing.txt"));} catch (IOException e) { System.out.println("تعذّر فتح الملف: " + e.getMessage());}تعذّر فتح الملف: missing.txtيحتاج هذا المثال إلى import java.io.IOException; و import java.nio.file.*; في أول الملف.
استثناء خاص بك
Section titled “استثناء خاص بك”للأخطاء الخاصة ببرنامجك، أنشئ كلاساً يرث من Exception (مفحوص) أو RuntimeException (غير مفحوص):
class InsufficientFundsException extends Exception { public InsufficientFundsException(double missing) { super("الرصيد لا يكفي، ينقصك " + missing); }}
class Account { private double balance = 100;
public void withdraw(double amount) throws InsufficientFundsException { if (amount > balance) { throw new InsufficientFundsException(amount - balance); } balance -= amount; System.out.println("تم السحب، الرصيد الآن " + balance); }}Account account = new Account();try { account.withdraw(30); account.withdraw(200);} catch (InsufficientFundsException e) { System.out.println(e.getMessage());}تم السحب، الرصيد الآن 70.0الرصيد لا يكفي، ينقصك 130.0اسم الاستثناء الواضح يشرح المشكلة لمن يقرأ الكود قبل أن يقرأ الرسالة نفسها.
جرّب بنفسك
Section titled “جرّب بنفسك”- اكتب دالة
divide(int a, int b)ترميIllegalArgumentExceptionبرسالة عربية إذا كانbصفراً. - استدعها داخل
try/catchمرتين: مرة بقيم سليمة ومرة بصفر. - أضف
finallyيطبع «انتهت العملية». - تحدٍّ: اكتب دالة
parseAge(String text)تحوّل النص إلى عدد، وترمي استثناءً خاصاً اسمهInvalidAgeExceptionإذا كان النص ليس رقماً أو كان العمر خارج المدى من 0 إلى 120.
أخطاء شائعة
Section titled “أخطاء شائعة”- catch فارغ: كتابة
catch (Exception e) {}تبتلع الخطأ بصمت، فتبحث ساعات عن مشكلة لا أثر لها. اطبع رسالة على الأقل. - التقاط Exception العامة دائماً: تخفي أخطاء لم تتوقعها. التقط النوع المحدد الذي تعرف كيف تعالجه.
- ترتيب خاطئ: وضع
catch (Exception e)قبل نوع أخص منه خطأ ترجمة، لأن القسم الثاني لن يصل إليه أحد. - استخدام الاستثناءات بدل الشروط: إذا استطعت فحص القيمة بـ
ifبسيطة فافعل، فالاستثناءات للحالات غير المعتادة.
اختبر نفسك
Section titled “اختبر نفسك”سؤال
متى يُنفَّذ الكود داخل finally؟
finally يعمل في الحالتين، ولذلك نضع فيه أعمال التنظيف مثل إغلاق الملفات.
سؤال
ما الفرق بين throw و throws؟
throw أمر تنفيذي داخل جسم الدالة، أما throws فتحذير لمن يستدعي الدالة.
الخلاصة
Section titled “الخلاصة”- الاستثناء كائن يصف خطأ وقت التشغيل، وإذا لم يُلتقط توقف البرنامج.
tryللكود الخطِر، وcatchلخطة الطوارئ، وfinallyلما يجب تنفيذه دائماً.throw new ...لرمي خطأ بنفسك عند استقبال قيمة غير منطقية.- الاستثناءات المفحوصة يجبرك المترجم على معالجتها أو إعلانها بـ
throws. - أنشئ استثناءك الخاص بوراثة
ExceptionأوRuntimeException.