חריגים ב- Java, חלק 1: יסודות הטיפול בחריגים

חריגים מג'אווה הם סוגי ספריות ותכונות שפה המשמשות לייצוג והתמודדות עם כשל בתכנית. אם רצית להבין כיצד מיוצג כישלון בקוד המקור, הגעת למקום הנכון. בנוסף לסקירה כללית על חריגים בג'אווה, אתחיל עם תכונות השפה של ג'אווה לזריקת חפצים, לנסות קוד שעשוי להיכשל, לתפוס חפצים מושלכים ולנקות את קוד הג'אווה שלך לאחר שהושלך חריג.

במחצית הראשונה של הדרכה זו תלמדו על תכונות שפה בסיסיות וסוגי ספריות שקיימים מאז Java 1.0. במחצית השנייה תגלה יכולות מתקדמות שהוצגו בגרסאות Java האחרונות.

שים לב כי דוגמאות קוד במדריך זה תואמות ל- JDK 12.

הורד קבל את הקוד הורד את קוד המקור למשל יישומים במדריך זה. נוצר על ידי ג'ף פרייזן עבור JavaWorld.

מהם יוצאי דופן בג'אווה?

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

חריגים נבדקו

Java מסווגת חריגים הנובעים מגורמים חיצוניים (כגון קובץ חסר) כחריגים מסומנים . מהדר Java בודק כי מטופלים (מתוקנים) חריגים כאלה במקום שהם מתרחשים או מתועדים לטיפול אחר.

מטפלי חריגים

מטפל חריג הוא רצף של קוד שמטפל חריגה. הוא חוקר את ההקשר - כלומר הוא קורא ערכים שנשמרו ממשתנים שהיו בהיקף בזמן שהחריג התרחש - ואז משתמש במה שהוא לומד כדי להחזיר את תוכנית Java לזרימה של התנהגות רגילה. לדוגמה, מטפל חריג עשוי לקרוא שם קובץ שמור ולבקש מהמשתמש להחליף את הקובץ החסר.

חריגים בזמן ריצה (לא מסומן)

נניח שתוכנית מנסה לחלק מספר שלם במספר שלם 0. חוסר אפשרות זה ממחיש סוג אחר של חריג, כלומר חריג בזמן ריצה . בניגוד לחריגים מסומנים, חריגים בזמן ריצה נובעים בדרך כלל מקוד מקור כתוב גרוע, ולכן יש לתקן אותם על ידי המתכנת. מכיוון שהמהדר לא בודק שמטפלים בחריגים בזמן ריצה או מתועדים לטיפול אחר במקום אחר, אתה יכול לחשוב על חריג של זמן ריצה כחריג לא מסומן .

על חריגים בזמן ריצה

ייתכן שתשנה תוכנית שתטפל בחריג זמן ריצה, אך עדיף לתקן את קוד המקור. יוצאי דופן בזמן הריצה נובעים מהעברת טיעונים לא חוקיים לשיטות הספרייה; יש לתקן את קוד שיחת הבאגי.

טעויות

כמה יוצאים מן הכלל הם חמורים מאוד מכיוון שהם מסכנים את יכולתה של תוכנית להמשיך בביצוע. לדוגמא, תוכנית מנסה להקצות זיכרון מה- JVM אך אין מספיק זיכרון פנוי בכדי לספק את הבקשה. מצב חמור נוסף מתרחש כאשר תוכנית מנסה לטעון קובץ כיתה באמצעות Class.forName()שיחת שיטה, אך קובץ הכיתה פגום. חריג מסוג זה ידוע כשגיאה . לעולם אל תנסה לטפל בעצמך בשגיאות מכיוון ש- JVM לא יוכל להתאושש מכך.

חריגים בקוד המקור

חריג עשוי להיות מיוצג בקוד המקור כקוד שגיאה או כאובייקט . אציג את שניהם ואראה לך מדוע עצמים עדיפים.

קודי שגיאה לעומת אובייקטים

שפות תכנות כגון C משתמשות בקודי שגיאה מבוססי שלמים כדי לייצג כשל וסיבות לכישלון - כלומר חריגים. להלן מספר דוגמאות:

if (chdir("C:\\temp")) printf("Unable to change to temp directory: %d\n", errno); FILE *fp = fopen("C:\\temp\\foo"); if (fp == NULL) printf("Unable to open foo: %d\n", errno);

chdir()הפונקציה C (שינוי ספרייה) מחזירה מספר שלם: 0 בהצלחה או -1 בכישלון. באופן דומה, fopen()הפונקציה C (פתיחת הקובץ) מחזירה מצביע לא מלא (כתובת שלמה ) FILEלמבנה בהצלחה או מצביע null (המיוצג על ידי קבוע NULL) בכישלון. בשני המקרים, כדי לזהות את החריג שגרם לכישלון, עליך לקרוא את errnoקוד השגיאה מבוסס השלמות של המשתנה הגלובלי .

קודי שגיאה מציגים כמה בעיות:

  • מספרים שלמים הם חסרי משמעות; הם אינם מתארים את החריגים שהם מייצגים. לדוגמא, מה פירוש 6?
  • שיוך הקשר לקוד שגיאה הוא מביך. לדוגמה, ייתכן שתרצה להעביר את שם הקובץ שלא ניתן היה לפתוח, אך היכן אתה הולך לאחסן את שם הקובץ?
  • המספרים השלמים הם שרירותיים, מה שעלול להוביל לבלבול בעת קריאת קוד המקור. לדוגמא, ציון if (!chdir("C:\\temp"))( !מסמל NOT) במקום if (chdir("C:\\temp"))לבדיקת כישלון ברור יותר. עם זאת, 0 נבחר כדי לציין הצלחה, ולכן if (chdir("C:\\temp"))יש לציין זאת כדי לבדוק אם הכישלון.
  • קל מדי להתעלם מקודי שגיאה, מה שעלול להוביל לקוד באגי. לדוגמה, המתכנת יכול לציין chdir("C:\\temp");ולהתעלם if (fp == NULL)מהבדיקה. יתר על כן, המתכנת אינו צריך לבחון errno. על ידי אי בדיקת כישלון, התוכנית מתנהגת בצורה לא יציבה כאשר אחת מהפונקציות מחזירה מחוון כשל.

כדי לפתור את הבעיות הללו, ג'אווה אימצה גישה חדשה לטיפול בחריגים. בג'אווה אנו משלבים אובייקטים המתארים חריגים עם מנגנון המבוסס על השלכה ותפיסה של אובייקטים אלה. להלן מספר יתרונות של שימוש באובייקטים לעומת קוד שגיאה לציון חריגים:

  • ניתן ליצור אובייקט ממחלקה עם שם משמעותי. לדוגמא, FileNotFoundException( java.ioבחבילה) משמעותי יותר מ- 6.
  • אובייקטים יכולים לאחסן הקשר בתחומים שונים. לדוגמא, באפשרותך לאחסן הודעה, שם הקובץ שלא ניתן היה לפתוח, המיקום האחרון בו פעולת הניתוח נכשלה ו / או פריטים אחרים בשדות האובייקט.
  • אינך משתמש ifבהצהרות כדי לבחון כישלון. במקום זאת, אובייקטים חריגים נזרקים למטפל הנפרד מקוד התוכנית. כתוצאה מכך, קוד המקור קל יותר לקריאה ופחות סביר שהוא יהיה באגי.

זורק ותתי הסוגים שלו

Java מספקת היררכיה של שיעורים המייצגים סוגים שונים של חריגים. שיעורים אלה מושרשים java.langשל החבילה Throwableבכיתה, יחד עם שלה Exception, RuntimeException, ו Errorsubclasses.

Throwableהוא מעמד העל האולטימטיבי בכל הנוגע לחריגים. Throwableניתן לזרוק (ולתפוס לאחר מכן) רק אובייקטים שנוצרו ומתתי מחלקותיו. חפצים כאלה ידועים כזרקים .

Throwableאובייקט מזוהה עם מסר פרט מתאר חריג. מספר בונים, כולל הצמד המתואר להלן, ניתנים ליצירת Throwableאובייקט עם או בלי הודעה מפורטת:

  • Throwable () יוצר הודעה Throwableללא פירוט. קונסטרוקטור זה מתאים למצבים בהם אין הקשר. לדוגמה, אתה רק רוצה לדעת שערימה ריקה או מלאה.
  • Throwable (מחרוזת הודעה) יוצר הודעה Throwableעם messageכמפורט. ניתן להעביר הודעה זו למשתמש ו / או להירשם.

Throwableמספק את String getMessage()השיטה להחזרת הודעת הפירוט. זה גם מספק שיטות שימושיות נוספות, אותן אציג בהמשך.

כיתת החריגה

Throwableיש שתי מחלקות משנה ישירות. אחת Exceptionמשיעורי המשנה הללו היא , המתארת ​​חריג הנובע מגורם חיצוני (כגון ניסיון לקרוא מקובץ לא קיים). Exceptionמצהיר על אותם בונים (עם רשימות פרמטרים זהים) כ- Throwable, וכל קונסטרוקטור קורא Throwableלעמיתו. שיטות Exceptionהירושה Throwable; הוא אינו מכריז על שיטות חדשות.

Java מספקת שיעורי חריגים רבים שמתחלקים ישירות Exception. להלן שלוש דוגמאות:

  • CloneNotSupportedException מאותת על ניסיון לשכפל אובייקט שהמחלקה שלו אינה מיישמת את Cloneableהממשק. שני הסוגים נמצאים java.langבאריזה.
  • IOException מסמן כי התרחש איזה כשל בקלט / פלט. סוג זה ממוקם java.ioבחבילה.
  • ParseException signals that a failure has occurred while parsing text. This type can be found in the java.text package.

Notice that each Exception subclass name ends with the word Exception. This convention makes it easy to identify the class's purpose.

You'll typically subclass Exception (or one of its subclasses) with your own exception classes (whose names should end with Exception). Here are a couple of custom subclass examples:

public class StackFullException extends Exception { } public class EmptyDirectoryException extends Exception { private String directoryName; public EmptyDirectoryException(String message, String directoryName) { super(message); this.directoryName = directoryName; } public String getDirectoryName() { return directoryName; } }

The first example describes an exception class that doesn't require a detail message. It's default noargument constructor invokes Exception(), which invokes Throwable().

The second example describes an exception class whose constructor requires a detail message and the name of the empty directory. The constructor invokes Exception(String message), which invokes Throwable(String message).

Objects instantiated from Exception or one of its subclasses (except for RuntimeException or one of its subclasses) are checked exceptions.

The RuntimeException class

Exception is directly subclassed by RuntimeException, which describes an exception most likely arising from poorly written code. RuntimeException declares the same constructors (with identical parameter lists) as Exception, and each constructor invokes its Exception counterpart. RuntimeException inherits Throwable's methods. It declares no new methods.

Java provides many exception classes that directly subclass RuntimeException. The following examples are all members of the java.lang package:

  • ArithmeticException signals an illegal arithmetic operation, such as attempting to divide an integer by 0.
  • IllegalArgumentException signals that an illegal or inappropriate argument has been passed to a method.
  • NullPointerException signals an attempt to invoke a method or access an instance field via the null reference.

Objects instantiated from RuntimeException or one of its subclasses are unchecked exceptions.

The Error class

Throwable's other direct subclass is Error, which describes a serious (even abnormal) problem that a reasonable application should not try to handle--such as running out of memory, overflowing the JVM's stack, or attempting to load a class that cannot be found. Like Exception, Error declares identical constructors to Throwable, inherits Throwable's methods, and doesn't declare any of its own methods.

You can identify Error subclasses from the convention that their class names end with Error. Examples include OutOfMemoryError, LinkageError, and StackOverflowError. All three types belong to the java.lang package.

Throwing exceptions

A C library function notifies calling code of an exception by setting the global errno variable to an error code and returning a failure code. In contrast, a Java method throws an object. Knowing how and when to throw exceptions is an essential aspect of effective Java programming. Throwing an exception involves two basic steps:

  1. Use the throw statement to throw an exception object.
  2. Use the throws clause to inform the compiler.

Later sections will focus on catching exceptions and cleaning up after them, but first let's learn more about throwables.

The throw statement

Java provides the throw statement to throw an object that describes an exception. Here's the syntax of the throw statement :

throw throwable;

The object identified by throwable is an instance of Throwable or any of its subclasses. However, you usually only throw objects instantiated from subclasses of Exception or RuntimeException. Here are a couple of examples:

throw new FileNotFoundException("unable to find file " + filename); throw new IllegalArgumentException("argument passed to count is less than zero");

The throwable is thrown from the current method to the JVM, which checks this method for a suitable handler. If not found, the JVM unwinds the method-call stack, looking for the closest calling method that can handle the exception described by the throwable. If it finds this method, it passes the throwable to the method's handler, whose code is executed to handle the exception. If no method is found to handle the exception, the JVM terminates with a suitable message.

The throws clause

You need to inform the compiler when you throw a checked exception out of a method. Do this by appending a throws clause to the method's header. This clause has the following syntax:

throws checkedExceptionClassName (, checkedExceptionClassName)*

A throws clause consists of keyword throws followed by a comma-separated list of the class names of checked exceptions thrown out of the method. Here is an example:

public static void main(String[] args) throws ClassNotFoundException { if (args.length != 1) { System.err.println("usage: java ... classfile"); return; } Class.forName(args[0]); }

This example attempts to load a classfile identified by a command-line argument. If Class.forName() cannot find the classfile, it throws a java.lang.ClassNotFoundException object, which is a checked exception.

Checked exception controversy

The throws clause and checked exceptions are controversial. Many developers hate being forced to specify throws or handle the checked exception(s). Learn more about this from my Are checked exceptions good or bad? blog post.