المقدمة
الاستثناء هو حدث خطأ يمكن أن يحدث أثناء تنفيذ برنامج ويعطل تدفقه الطبيعي. توفر لغة الجافا طريقة قوية وموجهة نحو الكائنات لمعالجة سيناريوهات الاستثناء المعروفة باسم معالجة الاستثناء في الجافا.
يمكن أن تنشأ الاستثناءات في الجافا من مواقف مختلفة مثل إدخال بيانات خاطئة من قبل المستخدم، فشل الأجهزة، فشل الاتصال بالشبكة، أو خادم قاعدة بيانات لا يعمل. الكود الذي يحدد ما يجب القيام به في سيناريوهات الاستثناء المحددة يُسمى معالجة الاستثناء.
رمي والتقاط الاستثناءات
تنشئ الجافا كائن استثناء عند حدوث خطأ أثناء تنفيذ بيان. يحتوي كائن الاستثناء على الكثير من معلومات التصحيح مثل تسلسل الطرق، ورقم الخط الذي حدث فيه الاستثناء، ونوع الاستثناء.
إذا حدث استثناء في الطريقة، يُطلق على عملية إنشاء كائن الاستثناء وتسليمه إلى بيئة التشغيل اسم “رمي الاستثناء“. يتوقف التدفق العادي للبرنامج وتحاول بيئة تشغيل جافا (JRE) البحث عن معالج للاستثناء. المعالج للاستثناء هو كتلة الشيفرة التي يمكنها معالجة كائن الاستثناء.
- منطق البحث عن معالج الاستثناء يبدأ بالبحث في الطريقة التي حدثت فيها الخطأ.
- إذا لم يتم العثور على معالج مناسب، فسينتقل إلى الطريقة المتصلة.
- وهكذا.
لذا، إذا كانت سلسلة استدعاء الطريقة هي A->B->C
وتم رفع استثناء في الطريقة C
، فإن عملية البحث عن معالج مناسب ستنتقل من C->B->A
.
إذا تم العثور على معالج استثناء مناسب، يتم تمرير كائن الاستثناء إلى المعالج لمعالجته. يُقال أن المعالج يقوم بـ “اصطياد الاستثناء“. إذا لم يكن هناك معالج استثناء مناسب، يتم إنهاء البرنامج وطباعة معلومات حول الاستثناء في وحدة التحكم.
إطار معالجة الاستثناء في جافا يُستخدم للتعامل مع أخطاء التشغيل فقط. يجب على المطور إصلاح أخطاء وقت الترجمة بنفسه، وإلا فإن البرنامج لن ينفذ.
كلمات رئيسية لمعالجة الاستثناء في جافا
توفر جافا كلمات مفتاحية محددة لأغراض التعامل مع الاستثناءات.
-
throw – نحن نعلم أنه إذا حدث خطأ، يتم إنشاء كائن استثناء وبعد ذلك يبدأ تشغيل جافا في معالجتها للتعامل معها. في بعض الأحيان قد نرغب في إثارة الاستثناءات بوضوح في كودنا. على سبيل المثال، في برنامج المصادقة للمستخدم، يجب أن نثير استثناءات إلى العملاء إذا كانت كلمة المرور
null
. تستخدم الكلمة المفتاحيةthrow
لإثارة الاستثناءات إلى تشغيل جافا للتعامل معها. -
throws – عندما نقوم بإثارة استثناء في طريقة ولا نتعامل معه، فإنه يجب علينا استخدام الكلمة المفتاحية
throws
في توقيع الطريقة لإعلام برنامج الاستدعاء بالاستثناءات التي قد تثار بواسطة الطريقة. يمكن للطريقة اللاحقة التعامل مع هذه الاستثناءات أو نقلها إلى طريقة المستدعي الخاصة بها باستخدام الكلمة المفتاحيةthrows
. يمكننا توفير استثناءات متعددة في شريحةthrows
، ويمكن استخدامها مع طريقةmain()
أيضًا. -
try-catch – نستخدم كتلة
try-catch
للتعامل مع الاستثناءات في كودنا.try
هو بداية الكتلة وcatch
هو في نهاية كتلةtry
للتعامل مع الاستثناءات. يمكن أن تحتوي كتلةtry-catch
على عدة كتلcatch
مع كتلةtry
. يمكن تضمين كتلةtry-catch
أيضًا. تتطلب كتلةcatch
معلمة يجب أن تكون من نوعException
. - أخيرًا – الكتلة
finally
اختيارية ويمكن استخدامها فقط مع كتلةtry-catch
. نظرًا لأن الاستثناء يوقف عملية التنفيذ، قد نكون لدينا بعض الموارد مفتوحة التي لن تُغلق، لذا يمكننا استخدام كتلةfinally
. تُنفذ الكتلةfinally
دائمًا، سواء حدث استثناء أم لا.
مثال على معالجة الاستثناء
- تُلقي الطريقة
testException()
استثناءات باستخدام الكلمة الأساسيةthrow
. تستخدم توقيع الطريقة كلمة الأساسيةthrows
لإعلام المُستدعي بنوع الاستثناءات التي قد تُلقيها. - في الطريقة
main()
، أقوم بمعالجة الاستثناءات باستخدام كتلةtry-catch
في الطريقةmain()
. عندما لا أقوم بمعالجته، أقوم بنقله إلى الوقت التنفيذي باستخدام جملةthrows
في الطريقةmain()
. - لا تُنفذ الطريقة
testException(-10)
أبدًا بسبب الاستثناء، ثم تُنفذ الكتلةfinally
.
تعتبر printStackTrace()
واحدة من الطرق المفيدة في فئة Exception
لأغراض تصحيح الأخطاء.
سينتج هذا الكود التالي:
Outputjava.io.FileNotFoundException: Negative Integer -5
at com.journaldev.exceptions.ExceptionHandling.testException(ExceptionHandling.java:24)
at com.journaldev.exceptions.ExceptionHandling.main(ExceptionHandling.java:10)
Releasing resources
Exception in thread "main" java.io.IOException: Only supported for index 0 to 10
at com.journaldev.exceptions.ExceptionHandling.testException(ExceptionHandling.java:27)
at com.journaldev.exceptions.ExceptionHandling.main(ExceptionHandling.java:19)
بعض النقاط المهمة للملاحظة:
- لا يمكن أن يكون لدينا جملة
catch
أوfinally
بدون بيانtry
. - A
try
statement should have eithercatch
block orfinally
block, it can have both blocks. - لا يمكننا كتابة أي كود بين كتل
try-catch-finally
. - يمكننا أن نمتلك عدة كتل
catch
مع عبارةtry
واحدة. - يمكن تداخل كتل
try-catch
مشابه لعباراتif-else
. - يمكننا أن نمتلك فقط كتلة
finally
واحدة مع عبارةtry-catch
.
تسلسل استثناءات جافا
كما ذكر سابقًا، عند رفع استثناء يتم إنشاء كائن استثناء. تكون استثناءات جافا هرمية ويتم استخدام التوريث لتصنيف أنواع مختلفة من الاستثناءات. Throwable
هو الفئة الأم لتسلسل استثناءات جافا ولديها كائنان فرعيان – Error
و Exception
. يتم تقسيم استثناءات Exception
إلى استثناءات تم التحقق منها واستثناءات تشغيلية.
- الأخطاء: الأخطاء هي سيناريوهات استثنائية خارج نطاق التطبيق، ولا يمكن التنبؤ بها أو استردادها. على سبيل المثال، فشل الأجهزة، تعطل جهاز الجافا الظاهري (JVM)، أو خطأ في الذاكرة. لهذا السبب لدينا تسلسل منفصل للأخطاء ويجب ألا نحاول التعامل مع هذه الحالات. بعض الأخطاء الشائعة هي
OutOfMemoryError
وStackOverflowError
. - الاستثناءات المدققة: الاستثناءات المدققة هي السيناريوهات الاستثنائية التي يمكننا توقعها في البرنامج ونحاول التعافي منها. على سبيل المثال،
FileNotFoundException
. يجب أن نقوم بالتقاط هذا الاستثناء وتقديم رسالة مفيدة للمستخدم وتسجيله بشكل مناسب لأغراض التصحيح. الاستثناء هو الفئة الأساسية لجميع الاستثناءات المدققة. إذا كنا نقوم برمي استثناء مدقق، يجب أن نلتقطه في نفس الطريقة، أو علينا أن ننقله إلى المستدعي باستخدام كلمة المفتاحthrows
. - استثناء تشغيلي: الاستثناءات التشغيلية تحدث بسبب سوء البرمجة. على سبيل المثال، محاولة استرجاع عنصر من مصفوفة. يجب أن نتحقق من طول المصفوفة أولاً قبل محاولة استرجاع العنصر، وإلا فقد يقوم برمي
ArrayIndexOutOfBoundException
أثناء التشغيل. الفئةRuntimeException
هي الفئة الأساسية لجميع استثناءات التشغيل. إذا كنا نقوم بـthrow
أي استثناء تشغيلي في طريقة، فليس من الضروري تحديده في توقيع الطريقة بجملةthrows
. يمكن تجنب الاستثناءات التشغيلية ببرمجة أفضل.
بعض الطرق المفيدة لفئات الاستثناء
الاستثناءات في جافا وجميع فصائلها لا توفر أي طرق محددة، وتم تعريف جميع الطرق في الفئة الأساسية – Throwable. يتم إنشاء فئات الاستثناء لتحديد أنواع مختلفة من سيناريوهات الاستثناء بحيث يمكننا تحديد سبب الجذر والتعامل مع الاستثناء وفقًا لنوعه. تنفذ فئة Throwable واجهة Serializable للتوافق بين البرامج.
بعض الطرق المفيدة في فئة Throwable هي:
- public String getMessage() – تُرجع هذه الطريقة سلسلة الرسائل لـ Throwable ويمكن توفير الرسالة أثناء إنشاء الاستثناء من خلال مُنشئه.
- public String getLocalizedMessage() – يتم توفير هذه الطريقة بحيث يمكن للفصائل تجاوزها لتوفير رسالة محددة للموقع لبرنامج الاستدعاء. تستخدم تنفيذ فئة Throwable لهذه الطريقة طريقة getMessage() لإرجاع رسالة الاستثناء.
- public synchronized Throwable getCause() – تُرجع هذه الطريقة سبب الاستثناء أو قيمة null إذا كان السبب غير معروف.
- public String toString() – تُرجع هذه الطريقة معلومات حول Throwable في تنسيق String، يحتوي السلسلة المُرجعة اسم فئة Throwable ورسالة موقع.
- public void printStackTrace() – تقوم هذه الطريقة بطباعة معلومات تتبع الكومة إلى تيار الخطأ القياسي، وهذه الطريقة متعددة الحملة، ويمكننا تمرير
PrintStream
أوPrintWriter
كمعلمة لكتابة معلومات تتبع الكومة إلى الملف أو التيار.
تحسينات إدارة الموارد التلقائية وكتلة الالتقاط في جافا 7
إذا كنت تقوم بـ catch
للكثير من الاستثناءات في كتلة try
واحدة، فستلاحظ أن كود كتلة الـ catch
يتكون في الغالب من كود متكرر لتسجيل الخطأ. في جافا 7، كانت إحدى الميزات تحسين كتلة الـ catch
حيث يمكننا التقاط العديد من الاستثناءات في كتلة catch
واحدة. فيما يلي مثال على كتلة الـ catch
بهذه الميزة:
هناك بعض القيود مثل أن كائن الاستثناء نهائي ولا يمكننا تعديله داخل كتلة catch
، اقرأ التحليل الكامل في تحسينات كتلة الـ catch في جافا 7.
معظم الوقت، نستخدم كتلة finally
فقط لإغلاق الموارد. أحيانًا ننسى إغلاقها ونحصل على استثناءات تشغيل عندما تستنفد الموارد. هذه الاستثناءات صعبة التصحيح، وقد نحتاج إلى النظر في كل مكان نستخدم فيه تلك المورد للتأكد من إغلاقه. في جافا 7، كانت إحدى التحسينات try-with-resources
حيث يمكننا إنشاء مورد في تعليمة try
نفسها واستخدامه داخل كتلة try-catch
. عندما تخرج التنفيذ من كتلة try-catch
، تقوم البيئة التشغيلية تلقائيًا بإغلاق هذه الموارد. إليك مثال على كتلة try-catch
مع هذا التحسين:
A Custom Exception Class Example
توفر جافا العديد من فئات الاستثناءات لنا للاستخدام، ولكن في بعض الأحيان قد نحتاج إلى إنشاء فئات استثناء مخصصة خاصة بنا. على سبيل المثال، لإخطار المتصل بنوع معين من الاستثناء مع الرسالة المناسبة. يمكننا أيضًا الحصول على حقول مخصصة لتتبع مثل رموز الخطأ. على سبيل المثال، دعونا نفترض أننا كتبنا طريقة لمعالجة ملفات النص فقط، حتى نتمكن من توفير رمز الخطأ المناسب للمتصل عند إرسال نوع ملف آخر كمدخل.
أولاً، أنشئ MyException
:
ثم، أنشئ CustomExceptionExample
:
يمكننا أيضًا أن نملك طريقة منفصلة لمعالجة أنواع مختلفة من رموز الأخطاء التي نحصل عليها من طرق مختلفة. يتم استهلاك بعضها لأنه قد لا نرغب في إخطار المستخدم بها، أو قد نعيدها لإخطار المستخدم بالمشكلة.
ها أنا أوسع Exception
بحيث يتعين التعامل مع هذا الاستثناء عند إنتاجه في الطريقة أو إعادته إلى برنامج النداء. إذا قمنا بتوسيع RuntimeException
، لا داعي لتحديده في عبارة throws
.
كانت هذه قرارات التصميم. يتيح استخدام Exception
المدروسة ميزة مساعدة المطورين على فهم الاستثناءات التي يمكن توقعها واتخاذ الإجراءات اللازمة للتعامل معها.
أفضل الممارسات لمعالجة الاستثناءات في جافا
- استخدام استثناءات محددة – لا تقدم فئات الأساس في هرم الاستثناء أي معلومات مفيدة، ولهذا السبب لدينا العديد من فئات الاستثناء في جافا، مثل
IOException
مع فئات فرعية أخرى مثلFileNotFoundException
،EOFException
، إلخ. يجب علينا دائمًا أن نقوم بـthrow
وcatch
لفئات الاستثناء المحددة حتى يعرف المتصل سبب الاستثناء بسهولة ويتمكن من معالجته. يجعل هذا التصحيح أسهل ويساعد تطبيقات العميل على التعامل مع الاستثناءات بشكل مناسب. - إلقاء الاستثناء في وقت مبكر أو الفشل السريع – يجب أن نحاول أن نلقي الاستثناءات في وقت مبكر إذا كان ذلك ممكنًا. اعتبر الطريقة
processFile()
أعلاه، إذا قمنا بتمرير وسيطnull
إلى هذه الطريقة، سنحصل على الاستثناء التالي:
OutputException in thread "main" java.lang.NullPointerException
at java.io.FileInputStream.<init>(FileInputStream.java:134)
at java.io.FileInputStream.<init>(FileInputStream.java:97)
at com.journaldev.exceptions.CustomExceptionExample.processFile(CustomExceptionExample.java:42)
at com.journaldev.exceptions.CustomExceptionExample.main(CustomExceptionExample.java:12)
أثناء عملية تصحيح الأخطاء، سنحتاج إلى النظر بعناية في تتبع الكومة لتحديد الموقع الفعلي للاستثناء. إذا قمنا بتغيير منطق التنفيذ الخاص بنا للتحقق من هذه الاستثناءات في وقت مبكر كما يلي:
فإن تتبع كومة الاستثناء سيوضح مكان حدوث الاستثناء مع رسالة واضحة:
Outputcom.journaldev.exceptions.MyException: File name can't be null
at com.journaldev.exceptions.CustomExceptionExample.processFile(CustomExceptionExample.java:37)
at com.journaldev.exceptions.CustomExceptionExample.main(CustomExceptionExample.java:12)
- Catch Late – حيث يفرض لغة الجافا التعامل سواءً بالتعامل مع الاستثناء المفحوص أو بإعلانه في توقيع الطريقة، في بعض الأحيان يميل المطورون إلى
catch
الاستثناء وتسجيل الخطأ. ولكن هذه الممارسة ضارة لأن برنامج الاتصال لا يحصل على أي إشعار بالاستثناء. يجب أن نقوم بـcatch
الاستثناءات فقط عندما نكون قادرين على التعامل معها بشكل مناسب. على سبيل المثال، في الطريقة أعلاه، أنا أقوم بـthrow
الاستثناءات مرة أخرى إلى الطريقة المتصلة للتعامل معها. يمكن استخدام نفس الطريقة من قبل تطبيقات أخرى قد ترغب في معالجة الاستثناء بطريقة مختلفة. أثناء تنفيذ أي ميزة، يجب علينا دائمًا أن نقوم بـthrow
الاستثناءات مرة أخرى إلى المتصل ونترك لهم أن يقرروا كيفية التعامل معها. - إغلاق الموارد – نظرًا لأن الاستثناءات توقف معالجة البرنامج، يجب علينا إغلاق جميع الموارد في كتلة finally أو استخدام تحسين Java 7
try-with-resources
للسماح لمحرك Java بإغلاقها بالنيابة عنك. - تسجيل الاستثناءات – يجب علينا دائمًا تسجيل رسائل الاستثناء وأثناء رمي الاستثناءات تقديم رسالة واضحة بحيث يعرف المتصل بسهولة سبب حدوث الاستثناء. يجب علينا دائمًا تجنب كتلة الـ
catch
الفارغة التي تستهلك الاستثناء فقط ولا توفر أي تفاصيل معنوية عن الاستثناء للتصحيح. - كتلة واحدة للتقاط الاستثناءات المتعددة – في معظم الأحيان نقوم بتسجيل تفاصيل الاستثناء وتقديم رسالة للمستخدم ، في هذه الحالة ، يجب علينا استخدام ميزة Java 7 لمعالجة الاستثناءات المتعددة في كتلة
catch
واحدة. سيؤدي هذا النهج إلى تقليل حجم الكود لدينا ، وسيبدو أكثر نظافة أيضًا. - استخدام الاستثناءات المخصصة – دائمًا ما يكون من الأفضل تحديد استراتيجية للتعامل مع الاستثناءات في وقت التصميم بدلاً من رمي والتقاط الاستثناءات المتعددة ، يمكننا إنشاء استثناء مخصص مع رمز خطأ ، ويمكن لبرنامج المتصل التعامل مع هذه الرموز الخطأ. من الفكرة الجيدة أيضًا إنشاء طريقة أداة لمعالجة رموز الأخطاء المختلفة واستخدامها.
- تعاريف الأسماء والتعبئة – عند إنشاء استثناء مخصص ، تأكد من أنه ينتهي بـ
Exception
حتى يكون واضحًا من الاسم نفسه أنه فئة استثناء. كما تأكد من تعبئتها مثلما هو مفعل في مجموعة أدوات تطوير Java (JDK). على سبيل المثال ،IOException
هو الاستثناء الأساسي لجميع عمليات IO. - استخدم الاستثناءات بحذر – الاستثناءات تكلف الكثير أحيانًا، وفي بعض الأحيان ليس من الضروري أن نقوم برمي الاستثناءات على الإطلاق، يمكننا بدلاً من ذلك إرجاع متغير بولياني لبرنامج النداء للإشارة إلى ما إذا كانت العملية ناجحة أم لا. يكون ذلك مفيدًا عندما تكون العملية اختيارية، ولا ترغب في أن يتعلق برنامجك بسبب فشله. على سبيل المثال، عند تحديث اقتباسات الأسهم في قاعدة البيانات من خدمة ويب من جهة ثالثة، قد نرغب في تجنب رمي الاستثناءات إذا فشل الاتصال.
- وثق الاستثناءات المرماة – استخدم Javadoc
@throws
لتحديد بوضوح الاستثناءات التي يتم رميها بواسطة الطريقة. يكون ذلك مفيدًا جدًا عندما تقدم واجهة لتستخدمها تطبيقات أخرى.
الختام
في هذا المقال، تعلمت حول التعامل مع الاستثناءات في لغة البرمجة جافا. تعلمت حول throw
و throws
. كما تعلمت حول كتل try
(و try-with-resources
)، catch
، و finally
.
Source:
https://www.digitalocean.com/community/tutorials/exception-handling-in-java