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

الاستثناءات المتحقق منها وغير المتحقق منها

15 دقيقة الدرس 4 من 14

الاستثناءات المتحقق منها وغير المتحقق منها

رأيت سابقًا كيف تُتيح لك try وcatch وfinally معالجة الاستثناءات وقت التشغيل. في هذا الدرس ستعرف أن Java تُقسّم جميع الاستثناءات إلى فئتين رئيسيتين — المتحقق منها وغير المتحقق منها — وأن المُترجم (compiler) نفسه يُطبّق قواعد إحداهما. فهم هذا الفرق ضروري لكتابة كود Java يُجمَّع بنجاح ويعالج الأخطاء على المستوى الصحيح.

الفئتان في لمحة سريعة

  • الاستثناءات المتحقق منها (Checked) — يعلم المُترجم أنها قد تحدث ويُجبرك على التعامل معها (إما التقاطها أو إعلان أن الدالة ترسلها للمُستدعي عبر throws).
  • الاستثناءات غير المتحقق منها (Unchecked) — لا يشترط المُترجم معالجتها. تُمثّل أخطاءً برمجية أو حالات غير متوقعة حقًا.
موقعها في التسلسل الهرمي: كل الاستثناءات المتحقق منها ترث من Exception مباشرةً (لكن ليس من RuntimeException). كل الاستثناءات غير المتحقق منها ترث من RuntimeException (التي ترث بدورها من Exception). فئات Error أيضًا غير متحقق منها، لكنها تُمثّل إخفاقات JVM خارج سيطرتك.

الاستثناءات المتحقق منها — يُطبّقها المُترجم

تُشير الاستثناءات المتحقق منها إلى حالة خارجة عن سيطرة برنامجك لكنها متوقعة — مثل ملف قد لا يوجد، أو اتصال شبكة قد يُرفض، أو قاعدة بيانات قد تكون غير متاحة. ولأن هذه المواقف متوقعة في العالم الحقيقي، تشترط Java أن تُقرّ بها صراحةً.

إذا كانت دالة قد تُطلق استثناءً متحقق منه، يجب عليك تنفيذ أحد الأمرين:

  1. تغليف الاستدعاء في كتلة try/catch ومعالجة الاستثناء هناك.
  2. الإعلان عن الاستثناء في توقيع الدالة بـ throws، مُحوّلًا المسؤولية للمُستدعي.

إن لم تفعل أيًّا منهما، لن يُجمَّع البرنامج. إليك مثالًا بسيطًا باستخدام FileNotFoundException، وهو استثناء متحقق منه تُطلقه FileReader:

import java.io.FileReader; import java.io.FileNotFoundException; public class CheckedDemo { // الخيار 1: معالجته هنا بـ try/catch public void readFileHandled() { try { FileReader reader = new FileReader("data.txt"); System.out.println("تم فتح الملف بنجاح."); } catch (FileNotFoundException e) { System.out.println("الملف غير موجود: " + e.getMessage()); } } // الخيار 2: الإعلان عنه وترك المعالجة للمُستدعي public void readFileDeclared() throws FileNotFoundException { FileReader reader = new FileReader("data.txt"); System.out.println("تم فتح الملف بنجاح."); } }
أيّ الخيارين تختار؟ عالج الاستثناء في الدالة التي تمتلك سياقًا كافيًا للتصرف بشكل مفيد (عرض رسالة، استخدام قيمة افتراضية، إعادة المحاولة). أعلنه بـ throws حين لا تعرف الطبقة الحالية ماذا تفعل — ربما طبقة واجهة المستخدم أو طبقة الخدمة الأعلى منها تستطيع الاستجابة بشكل أفضل.

أبرز الاستثناءات المتحقق منها في Java

  • IOException — قراءة أو كتابة الملفات أو التدفقات (streams) أو مقابس الشبكة.
  • FileNotFoundException — نوع محدد من IOException؛ مسار الملف غير موجود.
  • SQLException — خطأ عند التواصل مع قاعدة البيانات.
  • ParseException — تعذّر تحليل النص كتاريخ أو رقم.
  • ClassNotFoundException — لم تجد JVM فئة بالاسم المحدد وقت التشغيل (تُستخدم في الانعكاس).

الاستثناءات غير المتحقق منها — مفاجآت وقت التشغيل

تُشير الاستثناءات غير المتحقق منها في الغالب إلى خطأ برمجي — مررت null حيث كان كائن مطلوبًا، أو وصلت إلى فهرس مصفوفة غير موجود، أو حاولت القسمة على صفر. لا يُجبرك المُترجم على التقاطها لأنها لا ينبغي أن تحدث أصلًا في كود مكتوب بشكل صحيح.

public class UncheckedDemo { public static void main(String[] args) { // NullPointerException — استدعاء دالة على null String name = null; System.out.println(name.length()); // يُطلق NullPointerException // ArrayIndexOutOfBoundsException — فهرس خاطئ int[] numbers = {1, 2, 3}; System.out.println(numbers[5]); // يُطلق ArrayIndexOutOfBoundsException // ArithmeticException — القسمة على صفر int result = 10 / 0; // يُطلق ArithmeticException // NumberFormatException — تحويل نص غير صالح إلى عدد صحيح int value = Integer.parseInt("abc"); // يُطلق NumberFormatException } }

يمكنك التقاط الاستثناءات غير المتحقق منها إن كان لديك سبب وجيه (مثل معالج في المستوى الأعلى يسجّل الأعطال غير المتوقعة)، لكنك لست مُلزمًا بذلك.

أبرز الاستثناءات غير المتحقق منها (وقت التشغيل)

  • NullPointerException — استدعاء دالة أو الوصول إلى حقل على مرجع null.
  • ArrayIndexOutOfBoundsException — الفهرس سالب أو يتجاوز طول المصفوفة.
  • ArithmeticException — عملية حسابية غير قانونية، عادةً القسمة الصحيحة على صفر.
  • NumberFormatException — تعذّر تحليل النص كرقم.
  • IllegalArgumentException — استلمت الدالة وسيطًا غير منطقي.
  • IllegalStateException — الكائن في حالة غير ملائمة للعملية المطلوبة.
  • ClassCastException — تحويل غير صالح بين الأنواع.
  • StackOverflowError — استنفدت العودية اللانهائية كل مساحة المكدس (هذه Error وليست Exception).

لماذا يوجد هذا التمييز؟

فلسفة التصميم بسيطة: إذا كانت المشكلة متوقعة وقابلة للتعافي (الشبكة معطلة، الملف مفقود)، فينبغي للغة أن تُجبرك على التخطيط لها. أما إذا كانت المشكلة خطأ برمجيًا (نسيت التحقق من null، استخدمت فهرسًا خاطئًا)، فاللغة تثق بك لكتابة كود صحيح — إذ إن اشتراط كتلة catch في كل مكان سيُضيف ضوضاء دون تحسين الصحة البرمجية.

لا تُخمد الاستثناءات المتحقق منها. خطأ شائع لدى المبتدئين هو التقاط استثناء متحقق منه ثم عدم فعل شيء به — كتلة catch فارغة. هذا يُخفي المشاكل الحقيقية ويجعل التصحيح صعبًا للغاية. على الأقل سجّل الاستثناء أو أعد رميه.

مقارنة جنبًا إلى جنب

import java.io.FileReader; import java.io.IOException; public class Comparison { // متحقق منه: المُترجم يشترط try/catch أو إعلان throws public void checkedExample() throws IOException { FileReader fr = new FileReader("config.txt"); // IOException متحقق منه fr.close(); } // غير متحقق منه: لا اشتراط من المُترجم — لكن خطأ برمجي يُسبب عطلًا وقت التشغيل public void uncheckedExample() { String s = null; s.toUpperCase(); // NullPointerException — غير متحقق منه، لا يلزم throws } }

الخلاصة

تُمثّل الاستثناءات المتحقق منها إخفاقات خارجية متوقعة — إدخال/إخراج الملفات، قواعد البيانات، الشبكات. يضمن المُترجم إقرارك بها. أما الاستثناءات غير المتحقق منها فتُمثّل أخطاءً برمجية — فهارس خاطئة، إلغاء إشارة null، وسطاء غير صالحة. تُصلح السبب الجذري بدلًا من التقاطها في كل مكان. في الدرس التالي ستتعلم كيف تُطلق كلا النوعين بشكل صريح باستخدام throw وthrows.