أهلاً! اليوم سوف نلقي نظرة فاحصة على معالجة الاستثناءات في جافا. أكره أن أذكر ذلك، لكن جزءًا كبيرًا من عمل المبرمج هو التعامل مع الأخطاء. في أغلب الأحيان، خاصة به. اتضح أنه لا يوجد أشخاص لا يخطئون. ولا توجد مثل هذه البرامج أيضًا. بالطبع، عند التعامل مع الخطأ، فإن الشيء الرئيسي هو فهم سببه. والكثير من الأشياء يمكن أن تسبب أخطاء في البرنامج. في مرحلة ما، سأل منشئو Java أنفسهم ما الذي يجب فعله مع أخطاء البرمجة الأكثر احتمالية؟ تجنبها تمامًا ليس أمرًا واقعيًا، فالمبرمجون قادرون على كتابة أشياء لا يمكنك حتى تخيلها. :) لذلك، نحن بحاجة إلى إعطاء اللغة آلية للتعامل مع الأخطاء. بمعنى آخر، إذا كان هناك خطأ في برنامجك، فأنت بحاجة إلى نوع من البرنامج النصي لما يجب فعله بعد ذلك. ما الذي يجب أن يفعله البرنامج بالضبط عند حدوث خطأ؟ اليوم سوف نتعرف على هذه الآلية. يطلق عليه " الاستثناءات في جافا
".
ما هو الاستثناء؟
الاستثناء هو موقف استثنائي غير مخطط له يحدث أثناء تشغيل البرنامج. هناك العديد من الاستثناءات. على سبيل المثال، قمت بكتابة تعليمات برمجية تقرأ النص من ملف، وتعرض السطر الأول.public class Main {
public static void main(String[] args) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));
String firstString = reader.readLine();
System.out.println(firstString);
}
}
ولكن ماذا لو لم يكن هناك مثل هذا الملف! سيقوم البرنامج بإنشاء استثناء: FileNotFoundException
. الإخراج: استثناء في مؤشر الترابط "الرئيسي" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (لا يمكن للنظام العثور على المسار المحدد) في Java، يتم تمثيل كل استثناء بواسطة فئة منفصلة. كل فئات الاستثناء هذه مستمدة من "سلف" مشترك - Throwable
الفئة الأصل. عادةً ما يعكس اسم فئة الاستثناء بشكل موجز سبب حدوث الاستثناء:
FileNotFoundException
(لم يتم العثور على الملف)ArithmeticException
(حدث استثناء أثناء إجراء عملية حسابية)ArrayIndexOutOfBoundsException
(الفهرس خارج حدود المصفوفة). على سبيل المثال، يحدث هذا الاستثناء إذا حاولت عرض الموضع 23 من صفيف يحتوي على 10 عناصر فقط.
Exception in thread "main"
اههه. :/ هذا لا يساعد كثيرا. ليس من الواضح ماذا يعني الخطأ أو من أين جاء. لا توجد معلومات مفيدة هنا. لكن التنوع الكبير في فئات الاستثناء في Java يمنح المبرمج ما يهم أكثر: نوع الخطأ وسببه المحتمل (المضمن في اسم الفئة). إنه شيء آخر يجب رؤيته
Exception in thread "main" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (The system cannot find the specified path)
من الواضح على الفور ما هي المشكلة وأين نبدأ في الحفر لحل المشكلة! الاستثناءات، مثل مثيلات أي فئة، هي كائنات.
التقاط ومعالجة الاستثناءات في Java
تحتوي Java على كتل خاصة من التعليمات البرمجية للتعامل مع الاستثناءات:try
و catch
. يتم وضع الكود الذي يعتقد المبرمج أنه قد يحدث فيه استثناء في الكتلة. هذا لا يعني أنه سيحدث استثناء هنا. يعني أنه قد يحدث هنا، والمبرمج على علم بهذا الاحتمال. يتم وضع نوع الخطأ الذي تتوقع حدوثه في الكتلة . يحتوي هذا أيضًا على كافة التعليمات البرمجية التي يجب تنفيذها في حالة حدوث استثناء. هنا مثال: finally
try
catch
public static void main(String[] args) throws IOException {
try {
BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));
String firstString = reader.readLine();
System.out.println(firstString);
} catch (FileNotFoundException e) {
System.out.println("Error! File not found!");
}
}
الإخراج: خطأ! لم يتم العثور على الملف! نضع الكود الخاص بنا في كتلتين. في الكتلة الأولى، نتوقع احتمال حدوث خطأ "لم يتم العثور على الملف". هذه هي try
الكتلة. وفي الثانية، نخبر البرنامج بما يجب فعله في حالة حدوث خطأ. ونوع الخطأ المحدد: FileNotFoundException
. إذا وضعنا فئة استثناء مختلفة بين قوسين catch
، فلن FileNotFoundException
يتم اكتشافها.
public static void main(String[] args) throws IOException {
try {
BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));
String firstString = reader.readLine();
System.out.println(firstString);
} catch (ArithmeticException e) {
System.out.println("Error! File not found!");
}
}
الإخراج: استثناء في مؤشر الترابط "الرئيسي" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (يتعذر على النظام العثور على المسار المحدد) لم يتمcatch
تشغيل الكود الموجود في الكتلة، لأننا "قمنا بتكوين" تم التقاط هذه الكتلة ArithmeticException
، وألقى الكود الموجود في try
الكتلة نوعًا مختلفًا: FileNotFoundException
. لم نكتب أي تعليمات برمجية للتعامل معها FileNotFoundException
، لذلك يعرض البرنامج المعلومات الافتراضية لـ FileNotFoundException
. هنا عليك الانتباه إلى ثلاثة أشياء. رقم واحد. بمجرد حدوث استثناء على سطر ما في try
الكتلة، لن يتم تنفيذ التعليمات البرمجية التالية. تنفيذ البرنامج على الفور "يقفز" إلى catch
الكتلة. على سبيل المثال:
public static void main(String[] args) {
try {
System.out.println("Divide by zero");
System.out.println(366/0);// This line of code will throw an exception
System.out.println("This");
System.out.println("code");
System.out.println("will not");
System.out.println("be");
System.out.println("executed!");
} catch (ArithmeticException e) {
System.out.println("The program jumped to the catch block!");
System.out.println("Error! You can't divide by zero!");
}
}
الإخراج: القسمة على صفر قفز البرنامج إلى كتلة الالتقاط! خطأ! لا يمكنك القسمة على صفر! في السطر الثاني من try
الكتلة، نحاول القسمة على 0، مما يؤدي إلى ArithmeticException
. وبالتالي، try
لن يتم تنفيذ الأسطر من 3 إلى 10 من الكتلة. كما قلنا، يبدأ البرنامج على الفور في تنفيذ catch
الكتلة. الرقم اثنان. يمكن أن يكون هناك عدة catch
كتل. إذا كان الكود الموجود في try
الكتلة لا يرمي واحدًا، بل عدة أنواع مختلفة من الاستثناءات، فيمكنك كتابة كتلة catch
لكل منها.
public static void main(String[] args) throws IOException {
try {
BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));
System.out.println(366/0);
String firstString = reader.readLine();
System.out.println(firstString);
} catch (FileNotFoundException e) {
System.out.println("Error! File not found!");
} catch (ArithmeticException e) {
System.out.println("Error! Division by 0!");
}
}
في هذا المثال، قمنا بكتابة كتلتين catch
. إذا FileNotFoundException
حدث في الكتلة، فسيتم تنفيذ الكتلة try
الأولى . catch
في حالة ArithmeticException
حدوث ذلك، سيتم تنفيذ الكتلة الثانية. يمكنك كتابة 50 catch
قطعة إذا أردت ذلك. بالطبع، من الأفضل عدم كتابة تعليمات برمجية يمكنها طرح 50 نوعًا مختلفًا من الاستثناءات. :) ثالث. كيف تعرف الاستثناءات التي قد يطرحها الكود الخاص بك؟ حسنًا، قد تكون قادرًا على تخمين بعضها، لكن من المستحيل أن تبقي كل شيء في رأسك. لذلك يعرف مترجم Java الاستثناءات الأكثر شيوعًا والمواقف التي قد تحدث فيها. على سبيل المثال، إذا كتبت تعليمات برمجية يعرف المترجم أنها قد تؤدي إلى نوعين من الاستثناءات، فلن يتم تجميع التعليمات البرمجية الخاصة بك حتى تتعامل معها. وسنرى أمثلة على ذلك أدناه.
الآن بعض الكلمات حول معالجة الاستثناءات
هناك طريقتان للتعامل مع الاستثناءات في Java. لقد واجهنا بالفعل الأول: يمكن للطريقة التعامل مع الاستثناء نفسه فيcatch()
كتلة. هناك خيار ثانٍ: يمكن للأسلوب إعادة طرح الاستثناء في مكدس الاستدعاءات. ماذا يعني ذالك؟ على سبيل المثال، لدينا فئة بنفس printFirstString()
الطريقة، والتي تقرأ الملف وتعرض السطر الأول منه:
public static void printFirstString(String filePath) {
BufferedReader reader = new BufferedReader(new FileReader(filePath));
String firstString = reader.readLine();
System.out.println(firstString);
}
في الوقت الحاضر، لا يتم تجميع التعليمات البرمجية الخاصة بنا، لأنها تحتوي على استثناءات لم تتم معالجتها. في السطر 1، يمكنك تحديد المسار إلى الملف. يعرف المترجم أن مثل هذا الكود يمكن أن ينتج بسهولة ملف FileNotFoundException
. في السطر 3، تقرأ النص من الملف. يمكن أن تؤدي هذه العملية بسهولة إلى IOException
(خطأ في الإدخال/الإخراج). الآن يقول لك المترجم، "يا صديقي، لن أوافق على هذا الكود ولن أقوم بتجميعه حتى تخبرني بما يجب أن أفعله إذا حدث أحد هذه الاستثناءات. ويمكن أن يحدث ذلك بالتأكيد بناءً على الكود الذي كتبته !" لا توجد طريقة للالتفاف حول الأمر: أنت بحاجة إلى التعامل مع كليهما! نحن نعرف بالفعل الطريقة الأولى لمعالجة الاستثناءات: نحتاج إلى وضع الكود الخاص بنا في كتلة try
وإضافة catch
كتلتين:
public static void printFirstString(String filePath) {
try {
BufferedReader reader = new BufferedReader(new FileReader(filePath));
String firstString = reader.readLine();
System.out.println(firstString);
} catch (FileNotFoundException e) {
System.out.println("Error, file not found!");
e.printStackTrace();
} catch (IOException e) {
System.out.println("File input/output error!");
e.printStackTrace();
}
}
لكن هذا ليس الخيار الوحيد. يمكننا ببساطة رفع الاستثناء بدلاً من كتابة تعليمات برمجية لمعالجة الأخطاء داخل الطريقة.
معالجة استثناءات جافا للكلمات الأساسية
يتم ذلك باستخدام الكلمة الأساسيةthrows
في إعلان الطريقة:
public static void printFirstString(String filePath) throws FileNotFoundException, IOException {
BufferedReader reader = new BufferedReader(new FileReader(filePath));
String firstString = reader.readLine();
System.out.println(firstString);
}
بعد الكلمة الأساسية throws
، نشير إلى قائمة مفصولة بفواصل لجميع أنواع الاستثناءات التي قد تطرحها الطريقة. لماذا؟ الآن، إذا أراد شخص ما استدعاء printFirstString()
الطريقة في البرنامج، فسيتعين عليه (وليس أنت) تنفيذ معالجة الاستثناءات. على سبيل المثال، لنفترض أن أحد زملائك في مكان آخر من البرنامج كتب طريقة تستدعي طريقتك printFirstString()
:
public static void yourColleagueMethod() {
// Your colleague's method does something
//...and then calls your printFirstString() method with the file it needs
printFirstString("C:\\Users\\Henry\\Desktop\\testFile.txt");
}
نحصل على خطأ! لن يتم تجميع هذا الرمز! لم نكتب كود معالجة الاستثناءات في printFirstString()
الطريقة. ونتيجة لذلك، تقع هذه المهمة الآن على عاتق أولئك الذين يستخدمون هذه الطريقة. بمعنى آخر، methodWrittenByYourColleague()
لدى الطريقة الآن نفس الخيارين: يجب عليها إما استخدام كتلة try-catch
للتعامل مع كلا الاستثناءين، أو إعادة طرحهما.
public static void yourColleagueMethod() throws FileNotFoundException, IOException {
// The method does something
//...and then calls your printFirstString() method with the file it needs
printFirstString("C:\\Users\\Henry\\Desktop\\testFile.txt");
}
في الحالة الثانية، الطريقة التالية في مكدس الاستدعاءات - الطريقة التي تستدعي methodWrittenByYourColleague()
- يجب أن تتعامل مع الاستثناءات. لهذا السبب نسمي هذا "رمي الاستثناء أو تمريره". إذا قمت بطرح استثناءات لأعلى باستخدام الكلمة الأساسية throws
، فسيتم تجميع التعليمات البرمجية الخاصة بك. في هذه المرحلة، يبدو أن المترجم يقول: "حسنًا، حسنًا. تحتوي شفرتك على مجموعة من الاستثناءات المحتملة، لكنني سأقوم بتجميعها. لكننا سنعود إلى هذه المحادثة!" وعندما تستدعي أي عملية تحتوي على استثناءات لم تتم معالجتها، فإن المترجم يفي بوعده ويذكرك بها مرة أخرى. أخيرًا، سنتحدث عن finally
الحظر (آسف على التورية). هذا هو الجزء الأخير من try-catch-finally
الاستثناء الذي يعالج الثلاثي. والأمر مختلف لأنه يتم تنفيذه بغض النظر عما يحدث في البرنامج.
public static void main(String[] args) throws IOException {
try {
BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));
String firstString = reader.readLine();
System.out.println(firstString);
} catch (FileNotFoundException e) {
System.out.println("Error! File not found!");
e.printStackTrace();
} finally {
System.out.println ("And here's the finally block!");
}
}
finally
في هذا المثال، سيتم تنفيذ التعليمات البرمجية الموجودة داخل الكتلة في كلتا الحالتين. إذا تم تشغيل الكود الموجود في try
الكتلة بالكامل دون طرح أي استثناءات، finally
فسيتم تشغيل الكتلة في النهاية. إذا تمت مقاطعة التعليمات البرمجية الموجودة داخل try
الكتلة بواسطة استثناء وانتقل البرنامج إلى catch
الكتلة، finally
فستظل الكتلة تعمل بعد التعليمات البرمجية الموجودة داخل catch
الكتلة.
لماذا هذا ضروري؟
والغرض الرئيسي منه هو تنفيذ التعليمات البرمجية الإلزامية: التعليمات البرمجية التي يجب تنفيذها بغض النظر عن الظروف. على سبيل المثال، غالبًا ما يحرر بعض الموارد التي يستخدمها البرنامج. في الكود الخاص بنا، نفتح دفقًا لقراءة المعلومات من الملف وتمريرها إلى الكائنBufferedReader
. يجب علينا إغلاق القارئ لدينا وتحرير الموارد. يجب أن يتم ذلك مهما حدث، عندما يعمل البرنامج كما ينبغي، وعندما يُحدث استثناءً. تعد الكتلة finally
مكانًا مناسبًا جدًا للقيام بذلك:
public static void main(String[] args) throws IOException {
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));
String firstString = reader.readLine();
System.out.println(firstString);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
System.out.println ("And here's the finally block!");
if (reader != null) {
reader.close();
}
}
}
الآن نحن على يقين من أننا سوف نعتني بالموارد، بغض النظر عما يحدث عند تشغيل البرنامج. :) هذا ليس كل ما تحتاج لمعرفته حول الاستثناءات. تعد معالجة الأخطاء موضوعًا مهمًا للغاية في البرمجة. يتم تخصيص الكثير من المقالات لذلك. في الدرس التالي، سنكتشف أنواع الاستثناءات الموجودة وكيفية إنشاء الاستثناءات الخاصة بك. :) اراك لاحقا!
المزيد من القراءة: |
---|
GO TO FULL VERSION