חריגים מג'אווה הם סוגי ספריות ותכונות שפה המשמשות לייצוג והתמודדות עם כשל בתכנית. אם רצית להבין כיצד מיוצג כישלון בקוד המקור, הגעת למקום הנכון. בנוסף לסקירה כללית על חריגים בג'אווה, אתחיל עם תכונות השפה של ג'אווה לזריקת חפצים, לנסות קוד שעשוי להיכשל, לתפוס חפצים מושלכים ולנקות את קוד הג'אווה שלך לאחר שהושלך חריג.
במחצית הראשונה של הדרכה זו תלמדו על תכונות שפה בסיסיות וסוגי ספריות שקיימים מאז 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
, ו Error
subclasses.
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:
- Use the
throw
statement to throw an exception object. - 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.