CodeGym /בלוג Java /Random-HE /טיפול בחריגים ב-Java
John Squirrels
רָמָה
San Francisco

טיפול בחריגים ב-Java

פורסם בקבוצה
היי! היום נסקור מקרוב את הטיפול בחריגים ב-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. פלט: חריגה בשרשור "ראשי" 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 (The system cannot find the specified path)
ברור מיד מה יכולה להיות הבעיה ואיפה להתחיל לחפור כדי לפתור את הבעיה! חריגים, כמו מופעים של כל מחלקות, הם אובייקטים.

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

ל-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, אז 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!");
   }
}
פלט: חלק באפס התוכנית קפצה לבלוק ה-catch! שְׁגִיאָה! אי אפשר לחלק באפס! בשורה השנייה של tryהבלוק, אנו מנסים לחלק ב-0, וכתוצאה מכך ArithmeticException. כתוצאה מכך, שורות 3-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בלוקים. אם FileNotFoundExceptionמתרחשת בבלוק try, הבלוק הראשון catchיבוצע. אם ArithmeticExceptionמתרחש, הבלוק השני יבוצע. אתה יכול לכתוב 50 catchבלוקים אם אתה רוצה. כמובן, עדיף לא לכתוב קוד שיכול לגרום ל-50 סוגים שונים של חריגים. :) שלישית. איך אתה יודע אילו חריגים הקוד שלך עלול לזרוק? ובכן, אולי תוכל לנחש כמה מהם, אבל זה בלתי אפשרי עבורך לשמור הכל בראש. לכן, מהדר Java מכיר את החריגים הנפוצים ביותר ואת המצבים שבהם הם עלולים להתרחש. לדוגמה, אם אתה כותב קוד שהמהדר יודע שעלול לגרום לשני סוגים של חריגים, הקוד שלך לא יקמפל עד שתטפל בהם. נראה דוגמאות לכך להלן.

עכשיו כמה מילים על טיפול בחריגים

ישנן 2 דרכים לטיפול בחריגים ב-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();
   }
}
אבל זו לא האפשרות היחידה. נוכל פשוט לזרוק את החריג גבוה יותר במקום לכתוב קוד לטיפול בשגיאות בתוך השיטה.

טיפול בחריגים ב-Java מילות מפתח

זה נעשה באמצעות מילת המפתח 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()לשיטה יש כעת אותן 2 אפשרויות: עליה להשתמש בבלוק 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();
       }
   }
}
כעת אנו בטוחים שנדאג למשאבים, ללא קשר למה שקורה כאשר התוכנית פועלת. :) זה לא כל מה שאתה צריך לדעת על חריגים. טיפול בשגיאות הוא נושא סופר חשוב בתכנות. הרבה מאמרים מוקדשים לזה. בשיעור הבא, נגלה אילו סוגי חריגים קיימים וכיצד ליצור חריגים משלך. :) נתראה!
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION