JavaRush /בלוג Java /Random-HE /חריגים בג'אווה: תפיסה וטיפול

חריגים בג'אווה: תפיסה וטיפול

פורסם בקבוצה
שלום! אני שונא למסור לך את זה, אבל חלק עצום מהעבודה של מתכנת הוא התמודדות עם שגיאות. ולרוב - עם משלהם. במקרה שאין אנשים שלא עושים טעויות. וגם אין תוכניות כאלה. כמובן, העיקר כשעובדים על שגיאה הוא להבין את הסיבה שלה. ויכולות להיות חבורה שלמה של סיבות לכך בתוכנית. בשלב מסוים, היוצרים של Java עמדו בפני שאלה: מה לעשות עם השגיאות הפוטנציאליות האלה בתוכניות? הימנעות מהם לחלוטין אינה ריאלית. מתכנתים יכולים לכתוב משהו שאי אפשר אפילו לדמיין :) זה אומר שצריך לבנות בשפה מנגנון להתמודדות עם שגיאות. במילים אחרות, אם אירעה שגיאה כלשהי בתוכנית, יש צורך בסקריפט לעבודה נוספת. מה בדיוק על התוכנית לעשות כאשר מתרחשת שגיאה? היום נכיר את המנגנון הזה. וזה נקרא "חריגים " .

מה זה חריג בג'אווה

חריג הוא מצב חריג, לא מתוכנן, שהתרחש במהלך הפעלת התוכנית. יכולות להיות דוגמאות רבות לחריגים ב-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. סיכום:

Exception in thread "main" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (Системе не удается найти указанный путь)
כל חריג מיוצג על ידי מחלקה נפרדת ב-Java. כל מחלקות החריגות מגיעות מ"אב קדמון" משותף - מחלקת האב Throwable. השם של מחלקת החריג בדרך כלל משקף בקצרה את הסיבה להתרחשותה:
  • FileNotFoundException(הקובץ לא נמצא)
  • ArithmeticException(חריג בעת ביצוע פעולה מתמטית)
  • ArrayIndexOutOfBoundsException(מספר תא המערך מצוין מעבר לאורכו). לדוגמה, אם תנסה להוציא מערך תאים[23] למסוף עבור מערך מערך באורך 10.
יש כמעט 400 שיעורים כאלה בג'אווה! למה כל כך הרבה? בדיוק כדי שיהיה יותר נוח למתכנתים לעבוד איתם. תארו לעצמכם: כתבתם תוכנית, וכשהיא פועלת, היא זורקת חריג שנראה כך:
Exception in thread "main"
אה-אה :/ שום דבר לא ברור. לא ברור באיזה טעות מדובר ומאיפה היא הגיעה. אין מידע שימושי. אבל הודות למגוון כזה של מחלקות, המתכנת מקבל את העיקר לעצמו - סוג השגיאה והסיבה הסבירה שלה, המוכלת בשם המחלקה. אחרי הכל, זה דבר אחר לגמרי לראות בקונסולה:
Exception in thread "main" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (Системе не удается найти указанный путь)
מיד מתברר מה יכולה להיות הבעיה ו"באיזה כיוון לחפור" כדי לפתור את הבעיה! חריגים, כמו כל מופע של מחלקות, הם אובייקטים.

חריגים בתפיסה ובטיפול

כדי לעבוד עם חריגים ב-Java, ישנם בלוקי קוד מיוחדים: try, catchו finally. חריגים: יירוט ועיבוד - 2הקוד שבו המתכנת מצפה שיתרחשו חריגים ממוקם בבלוק 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מחלקה חריגה נוספת לסוגריים של הבלוק, הוא לא ייתפס.
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!");
   }
}
סיכום:

Exception in thread "main" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (Системе не удается найти указанный путь)
הקוד בבלוק catchלא עבד כי "הגדרנו" את הבלוק הזה ליירט ArithmeticException, והקוד בבלוק tryזרק סוג אחר - FileNotFoundException. לא כתבנו סקריפט עבור FileNotFoundException, אז התוכנית הציגה בקונסולה את המידע שמוצג כברירת מחדל עבור FileNotFoundException. כאן אתה צריך לשים לב ל-3 דברים. ראשון. ברגע שמתרחש חריג בכל שורת קוד בבלוק ניסיון, הקוד שאחריו לא יבוצע עוד. ביצוע התוכנית "יקפוץ" מיד לבלוק catch. לדוגמה:
public static void main(String[] args) {
   try {
       System.out.println("Divide a number 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("Not");
       System.out.println("will");
       System.out.println("done!");

   } catch (ArithmeticException e) {

       System.out.println("The program jumped to the catch block!");
       System.out.println("Error! You can't divide by zero!");
   }
}
סיכום:

Делим число на ноль 
Программа перепрыгнула в блок catch! 
Ошибка! Нельзя делить на ноль! 
בבלוק tryבשורה השנייה, ניסינו לחלק מספר ב-0, מה שהביא לחריגה ArithmeticException. לאחר מכן, שורות 6-10 של הבלוק tryלא יבוצעו יותר. כפי שאמרנו, התוכנית החלה מיד לבצע את הבלוק 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. אם, tryמתרחש בבלוק FileNotFoundException, הבלוק הראשון יבוצע catch. אם יקרה ArithmeticException, השני יבוצע. אתה יכול לכתוב לפחות 50 בלוקים catchאבל, כמובן, עדיף לא לכתוב קוד שיכול לזרוק 50 סוגים שונים של שגיאות :) שלישית. איך אתה יודע אילו חריגים הקוד שלך עלול לזרוק? ובכן, אתה יכול, כמובן, לנחש על כמה, אבל זה בלתי אפשרי לשמור הכל בראש שלך. לכן, מהדר Java יודע על החריגים הנפוצים ביותר ויודע באילו מצבים הם יכולים להתרחש. לדוגמה, אם כתבת קוד והקומפיילר יודע ש-2 סוגים של חריגים עלולים להתרחש במהלך פעולתו, הקוד שלך לא יקמפל עד שתטפל בהם. נראה דוגמאות לכך להלן. עכשיו לגבי טיפול בחריגים. יש 2 דרכים לעבד אותם. כבר פגשנו את הראשון - השיטה יכולה להתמודד עם החריגה באופן עצמאי בבלוק 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בתהליך זה , שגיאה במהלך קלט-פלט (Input-Output) יכולה להתרחש בקלות . עכשיו המהדר אומר לך, "אחי, אני לא אאשר את הקוד הזה או ארכיב אותו עד שתגיד לי מה עלי לעשות אם אחד מהחריגים האלה מתרחש. והם בהחלט יכולים לקרות על סמך הקוד שכתבת!" . אין לאן ללכת, אתה צריך לעבד את שניהם! אפשרות העיבוד הראשונה כבר מוכרת לנו: עלינו למקם את הקוד שלנו בבלוק 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("Error while inputting/outputting data from file!");
       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 at one moment calls your printFirstString() method with the file it needs
   printFirstString("C:\\Users\\Eugene\\Desktop\\testFile.txt");
}
שגיאה, הקוד אינו קומפילציה! printFirstString()לא כתבנו סקריפט לטיפול בשגיאות בשיטה . לכן, המשימה מוטלת על כתפיהם של מי שישתמש בשיטה זו. כלומר, השיטה yourColleagueMethod()עומדת כעת בפני אותן 2 אפשרויות: עליה לעבד את שני החריגים ש"מגיעים" אליה באמצעות try-catch, או להעביר אותם הלאה.
public static void yourColleagueMethod() throws FileNotFoundException, IOException {
   //...the method does something

   //...and at one moment calls your printFirstString() method with the file it needs
   printFirstString("C:\\Users\\Eugene\\Desktop\\testFile.txt");
}
במקרה השני, העיבוד ייפול על הכתפיים של השיטה הבאה על המחסנית - זו שתקרא yourColleagueMethod(). לכן מנגנון כזה נקרא "השלכת חריגה כלפי מעלה", או "מעבר לפסגה". כאשר אתה מעלה חריגים באמצעות 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 is the finally block!");
   }
}
בדוגמה זו, הקוד בתוך הבלוק finallyמבוצע בשני המקרים. אם הקוד בבלוק tryמבוצע במלואו ואינו זורק חריג, הבלוק יופעל בסוף finally. אם הקוד בפנים tryמופרע והתוכנית קופצת לבלוק catch, לאחר ביצוע הקוד בפנים catch, הבלוק עדיין ייבחר finally. למה זה נחוץ? מטרתו העיקרית היא לבצע את החלק הנדרש בקוד; אותו חלק שיש להשלים ללא קשר לנסיבות. לדוגמה, לעתים קרובות זה משחרר כמה משאבים המשמשים את התוכנית. בקוד שלנו, אנו פותחים זרם כדי לקרוא מידע מקובץ ולהעביר אותו ל- BufferedReader. שלנו readerצריך להיסגר ולפנות משאבים. זה חייב להיעשות בכל מקרה: זה לא משנה אם התוכנית פועלת כמצופה או זורקת חריגה. זה נוח לעשות זאת בבלוק 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 is the finally block!");
       if (reader != null) {
           reader.close();
       }
   }
}
כעת אנו בטוחים לחלוטין שדאגנו למשאבים תפוסים, ללא קשר למה שקורה בזמן שהתוכנית פועלת :) זה לא כל מה שאתה צריך לדעת על חריגים. טיפול בשגיאות הוא נושא חשוב מאוד בתכנות: יותר ממאמר אחד מוקדש לו. בשיעור הבא נלמד אילו סוגי חריגים יש ואיך ליצור חריג משלך :) נתראה שם!
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION