تستخدم لغة البرمجة 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.
في هذا التمرين ستقوم ببناء معالجة الأخطاء لآلة حاسبة بسيطة للأعداد الصحيحة. ولتبسيط الأمور، تم توفير طرق لحساب الجمع والضرب والقسمة.
الهدف هو الحصول على آلة حاسبة تعمل وتُرجع سلسلة نصية بالنمط التالي: 16 + 51 = 67، عند تزويدها بالوسائط 16 و51 و+.
CalculatorConundrum calculator = new CalculatorConundrum();
calculator.calculate(16, 51, "+");
// => returns "16 + 51 = 67"
calculator.calculate(32, 6, "*");
// => returns "32 * 6 = 192"
calculator.calculate(512, 4, "/");
// => returns "512 / 4 = 128"
الطريقة الرئيسية للتنفيذ في هذه المهمة ستكون الطريقة CalculatorConundrum.calculate().
تأخذ هذه الطريقة ثلاثة وسائط.
الوسيطان الأولان operand1 وoperand2 هما عددان صحيحان ستُجرى عليهما العملية.
الوسيط الثالث operation من النوع String، ومن الضروري في هذا التمرين تنفيذ العمليات التالية:
+
*
/
حدّث الطريقة CalculatorConundrum.calculate() لتتعامل مع العمليات غير الصالحة:
operation هو null، يجب رمي استثناء IllegalArgumentException برسالة Operation cannot be null.operation هو ""، يجب رمي استثناء IllegalArgumentException برسالة Operation cannot be empty.operation أي عملية أخرى غير + أو * أو /، يجب رمي استثناء IllegalOperationException برسالة Operation '{operation}' does not exist.calculator.calculate(10, 1, null);
// => throws IllegalArgumentException with message "Operation cannot be null"
calculator.calculate(10, 1, "");
// => throws IllegalArgumentException with message "Operation cannot be empty"
calculator.calculate(10, 1, "-");
// => throws IllegalOperationException with message "Operation '-' does not exist"
في Java، عند محاولة القسمة على صفر يُرمى استثناء ArithmeticException.
حدّث الطريقة CalculatorConundrum.calculate() لتلتقط هذا الاستثناء ثم ترمي استثناء IllegalOperationException برسالة Division by zero is not allowed والاستثناء ArithmeticException المُلتقط كسبب له.
calculator.calculate(512, 0, "/");
// => throws IllegalOperationException with message "Division by zero is not allowed"
سجّل في Exercism لتتعلّم وتتقن Java عبر 26 مفهومًا158 تمرينًا، وإرشاد بشري حقيقي، وكل ذلك مجانًا.