تستخدم لغة البرمجة Java الاستثناءات للتعامل مع الأخطاء وغيرها من الأحداث الاستثنائية.
الاستثناء حدث يقع أثناء تنفيذ البرنامج فيعطّل التدفق الطبيعي للتعليمات. تُطرح الاستثناءات في Java بشكل صريح، وتُسمّى عملية طرح الاستثناء رمي استثناء. أما عملية التعامل مع الاستثناء فتُسمّى التقاط استثناء.
في Java، جميع الاستثناءات أصناف فرعية للصنف Exception، وهو نفسه صنف فرعي للصنف Throwable.
وتميّز Java بين نوعين من الاستثناءات:
الاستثناءات المُتحقَّق منها هي حالات استثنائية ينبغي للتطبيق أن يتوقعها وأن يتعافى منها.
ومن أمثلتها الاستثناء FileNotFoundException، ويحدث عندما تحاول طريقة قراءة ملف غير موجود.
يُتحقَّق من هذا النوع من الاستثناءات في وقت الترجمة: يجب أن تحدّد الطرق التي ترمي استثناءات مُتحقَّق منها ذلك في توقيع الطريقة، وعلى الكود الذي يستدعي طريقة قد ترمي استثناءً مُتحقَّق منه أن يتعامل معه، وإلا لن تتم ترجمة الكود.
جميع الاستثناءات المُتحقَّق منها هي أصناف فرعية للصنف Exception ولا ترث من RuntimeException.
الاستثناءات غير المُتحقَّق منها هي حالات استثنائية لا يستطيع التطبيق عادةً توقعها أو التعافي منها.
ومن أمثلتها الاستثناء NullPointerException، ويحدث عندما تتوقع طريقة قيمةً ليست null لكنها تستقبل null.
لا يُتحقَّق من هذا النوع من الاستثناءات في وقت الترجمة: لا يُشترط على الطرق التي ترمي استثناءات غير مُتحقَّق منها أن تذكر ذلك في توقيع الطريقة، ولا يُشترط على الكود الذي يستدعي طريقة قد ترمي استثناءً غير مُتحقَّق منه أن يتعامل معه.
ترث جميع الاستثناءات غير المُتحقَّق منها من RuntimeException، وهو نفسه امتداد للصنف Exception.
يمكن لطريقة في Java أن ترمي استثناءً باستخدام عبارة throw.
عند رمي استثناء مُتحقَّق منه من طريقة، يجب تحديد ذلك في توقيع الطريقة باستخدام الكلمة المفتاحية throws، كما يوضح المثال التالي.
وهذا يُلزم الكود المستدعي بتوقع احتمال رمي استثناء والتعامل معه وفقًا لذلك.
public class InsufficientBalanceException extends Exception {
}
public class BankAccount {
public void withdraw(double amount) throws InsufficientBalanceException {
if (balance < amount) {
throw new InsufficientBalanceException();
}
// rest of the method implementation
}
}
عند رمي استثناء غير مُتحقَّق منه من طريقة، لا يجب تحديد ذلك في توقيع الطريقة، وإن كان ذلك مدعومًا.
public class BankAccount {
public void withdraw(double amount) {
if (amount < 0) {
throw new IllegalArgumentException("Cannot withdraw a negative amount");
}
// rest of the method implementation
}
}
يتم التعامل مع الاستثناءات في Java باستخدام الكلمات المفتاحية try وcatch وfinally.
try.try كتلة catch واحدة أو أكثر تلتقط الاستثناءات المرمية داخل كتلة try.catch كتلة finally تُنفَّذ دائمًا بعد كتلة try، سواء رُمي استثناء أم لم يُرمَ.يوضح المثال التالي كيفية عمل هذه الكلمات المفتاحية:
public class ATM {
public void withdraw(BankAccount bankAccount, double amount) {
try {
System.out.println("Withdrawing " + amount);
bankAccount.withdraw(amount);
System.out.println("Withdrawal succeeded");
} catch (InsufficientBalanceException) {
System.out.println("Withdrawal failed: insufficient balance");
} catch (RuntimeException e) {
System.out.println("Withdrawal failed: " + e.getMessage());
} finally {
System.out.println("Current balance: " + bankAccount.getBalance());
}
}
}
في هذا المثال، عندما لا يُرمى أي استثناء، تُطبع المخرجات التالية:
Withdrawing 10.0
Withdrawal succeeded
Current balance: 5.0
لكن إن كانت عبارة bankAccount.withdraw(amount) ترمي استثناء InsufficientBalanceException، فستُطبع المخرجات التالية:
Withdrawing 10.0
Withdrawal failed: insufficient balance
Current balance: 5.0
أو، في حال رمي استثناء غير مُتحقَّق منه بواسطة bankAccount.withdraw(amount)، تُطبع المخرجات التالية:
Withdrawing -10.0
Withdrawal failed: Cannot withdraw a negative amount
Current balance: 5.0
لدى Java أيضًا فئة منفصلة تُسمّى الأخطاء، وهي مشكلات خطيرة تنشأ خارج التطبيق.
ومن أمثلتها الخطأ OutOfMemoryError، ويحدث عندما يحاول تطبيق استخدام ذاكرة أكثر مما هو متاح في النظام.
ومثل الاستثناءات غير المُتحقَّق منها، لا يُتحقَّق من الأخطاء في وقت الترجمة. والفرق أن هذه الأخطاء تمثل مشكلات على مستوى النظام، وعادةً ما ترميها آلة Java الافتراضية أو البيئة بدلًا من التطبيق. وينبغي للتطبيقات عمومًا ألا تحاول التقاط هذه الأخطاء أو التعامل معها.
ترث جميع الأخطاء في Java من الصنف Error.
كما ذكرنا سابقًا، الاستثناءات أحداث تعطّل التدفق الطبيعي للتعليمات، وتُستخدم للتعامل مع الأحداث الاستثنائية. لذلك لا يُنصح باستخدام الاستثناءات للتحكم في التدفق في تطبيقك.