Java exceptions are a consistency mechanism provided by Java to identify and respond to errors.
The Java exception mechanism can separate the exception handling code in the program from normal business code, ensure that the program code is more elegant and improve the program robustness. When using exceptions effectively, the exception can clearly answer these three questions: the exception type answers "what" and the exception stack trace answers "where" and the exception information answers "why" is thrown.
Several keywords used in the Java exception mechanism: try, catch, finally, throw, throws.
| Keywords | illustrate |
|---|---|
| try | Used for listening. Put the code to be listened to (code that may throw an exception) in the try statement block. When an exception occurs in the try statement block, the exception is thrown. |
| catch | Used to catch exceptions. catch is used to catch exceptions that occur in try statement blocks. |
| Finally | Finally statement blocks will always be executed. It is mainly used to recycle material resources (such as database connections, network connections and disk files) opened in try blocks. Only after the execution of the finally block will return or throw statements in the try or catch block. If the final statements such as return or throw are used, they will not jump back to execution and will stop directly. |
| throw | Used to throw exceptions. |
| throws | Used in method signatures to declare exceptions that may be thrown by the method. |
public class Demo1 { public static void main(String[] args) { try { int i = 10/0; System.out.println("i="+i); } catch (ArithmeticException e) { System.out.println("Caught Exception"); System.out.println("e.getMessage(): " + e.getMessage()); System.out.println("e.toString(): " + e.toString()); System.out.println("e.printStackTrace():"); e.printStackTrace(); } }} Running results:
Caught Exceptione.getMessage(): / by zeroe.toString(): java.lang.ArithmeticException: / by zeroe.printStackTrace():java.lang.ArithmeticException: / by zero at Demo1.main(Demo1.java:6)
The result description: There is an operation with a divisor of 0 in the try statement block, and the operation will throw a java.lang.ArithmeticException exception. By catch, the exception is caught.
Observing the results we found that System.out.println("i="+i) was not executed. This means that after an exception occurs in the try statement block, the remaining content in the try statement block will no longer be executed.
Example 2: Understand the basic usage of finally
Based on "Example One", we add finally statements.
public class Demo2 { public static void main(String[] args) { try { int i = 10/0; System.out.println("i="+i); } catch (ArithmeticException e) { System.out.println("Caught Exception"); System.out.println("e.getMessage(): " + e.getMessage()); System.out.println("e.toString(): " + e.toString()); System.out.println("e.printStackTrace():"); e.printStackTrace(); } finally { System.out.println("run finally"); } }} Running results:
Caught Exceptione.getMessage(): / by zeroe.toString(): java.lang.ArithmeticException: / by zeroe.printStackTrace():java.lang.ArithmeticException: / by zero at Demo2.main(Demo2.java:6) run finally
Results: Finally statement block was executed.
Example 3: Understand the basic usage of throws and throws
throws is used to declare thrown exceptions, while throw is used to throw exceptions.
class MyException extends Exception { public MyException() {} public MyException(String msg) { super(msg); }}public class Demo3 { public static void main(String[] args) { try { test(); } catch (MyException e) { System.out.println("Catch My Exception"); e.printStackTrace(); } } public static void test() throws MyException{ try { int i = 10/0; System.out.println("i="+i); } catch (ArithmeticException e) { throw new MyException("This is MyException"); } }} Running results:
Catch My ExceptionMyException: This is MyException at Demo3.test(Demo3.java:24) at Demo3.main(Demo3.java:13)
Results: MyException is a subclass inherited from Exception. ArithmeticException exception (divider is 0) is generated in the try statement block of test(), and the exception is caught in the catch; then a MyException exception is thrown. The main() method captures the MyException thrown in test().
Java Exception Framework
Java exception architecture diagram:
1. Throwable
Throwable is a superclass of all errors or exceptions in the Java language.
Throwable contains two subclasses: Error and Exception. They are usually used to indicate an abnormality has occurred.
Throwable contains snapshots of the thread executing the stack when its thread is created. It provides interfaces such as printStackTrace() to obtain information such as stack trace data.
2. Exception
Exception and its subclasses are a form of Throwable that points out the conditions that a reasonable application wants to capture.
3. RuntimeException
RuntimeException is a superclass that may throw exceptions during normal operation of the Java virtual machine.
The compiler does not check for RuntimeException exceptions. For example, when the divisor is zero, an ArithmeticException exception is thrown. RuntimeException is a superclass of ArithmeticException. When the code has a divisor of zero, it can also be compiled if it "is not thrown through throws declaration" or "is not handled through try...catch...". This is what we say "the compiler won't check for RuntimeException exceptions"!
If the code generates a RuntimeException exception, it needs to be avoided by modifying the code. For example, if the divisor is zero, you need to avoid this situation through code!
4. Error
Like Exception, Error is also a subclass of Throwable. It is used to indicate serious problems that a reasonable application should not attempt to catch, and most such errors are exceptional conditions.
Like RuntimeException, the compiler will not check for an Error.
Java divides the throwable structure into three types: checked exception (Checked Exception), runtime exception (RuntimeException) and error (Error).
(1) Runtime exception
Definition: RuntimeException and its subclasses are called runtime exceptions.
Features: The Java compiler will not check it. That is to say, when such an exception may occur in the program, if it is "not thrown through the throws declaration" or "not caught with the try-catch statement", it will still be compiled and passed. For example, the ArithmeticException exception generated when the divisor is zero, the IndexOutOfBoundsException exception generated when the array is out of bounds, the ConcurrentModificationException exception generated by the fail-fail mechanism, etc., are all runtime exceptions.
Although the Java compiler does not check for runtime exceptions, we can also declare and throw it through throws, or capture it through try-catch.
If a runtime exception is generated, it needs to be avoided by modifying the code. For example, if the divisor is zero, you need to avoid this situation through code!
(2) Checked exception
Definition: The Exception class itself, and other subclasses in Exception's subclasses except "runtime exception" are all considered checked exceptions.
Features: The Java compiler will check it. Such exceptions are either declared and thrown through throws or captured through try-catch, otherwise they cannot be compiled. For example, CloneNotSupportedException is a checked exception. When an object is cloned through the clone() interface, and the corresponding class of the object does not implement the Cloneable interface, a CloneNotSupportedException will be thrown.
Exceptions being checked can usually be recovered.
(3) Error
Definition: Error class and its subclasses.
Features: Like runtime exceptions, the compiler will not check for errors.
An error occurs when there is insufficient resources, failure of constraints, or other conditions that cannot continue to run by other programs. The program itself cannot fix these errors. For example, VirtualMachineError is an error.
According to Java convention, we should not implement any new Error subclasses!
For the above three structures, which one should we throw an exception or an error? The recommendation given in "Effective Java" is: use checked exceptions for conditions that can be recovered, and use runtime exceptions for program errors.
Several suggestions on exception handling
Article 1: Use exceptions only for abnormal situations
Recommendation: Exceptions should only be used for abnormal conditions, and they should never be used for normal control flows.
Explanation is made by comparing the two codes below.
Code 1
try { int i=0; while (true) { arr[i]=0; i++; }} catch (IndexOutOfBoundsException e) {}Code 2for (int i=0; i<arr.length; i++) { arr[i]=0;} The purpose of both codes is to iterate over the arr array and set the value of each element in the array to 0. Code 1 terminates by exception, which seems very difficult to understand, Code 2 terminates by array boundaries. We should avoid using Code 1 for three main reasons:
The original design of exception mechanisms is for abnormal situations, so few JVM implementations try to optimize their performance. Therefore, the overhead of creating, throwing, and catching exceptions is expensive.
Putting the code in try-catch returns prevents the JVM from implementing certain specific optimizations that might have been to perform.
The standard pattern of traversing arrays does not lead to redundant checks, and some modern JVM implementations will optimize them.
In fact, exception-based modes are much slower than standard modes. The test code is as follows:
public class Advice1 { private static int[] arr = new int[]{1,2,3,4,5}; private static int SIZE = 10000; public static void main(String[] args) { long s1 = System.currentTimeMillis(); for (int i=0; i<SIZE; i++) endByRange(arr); long e1 = System.currentTimeMillis(); System.out.println("endByRange time:"+(e1-s1)+"ms" ); long s2 = System.currentTimeMillis(); for (int i=0; i<SIZE; i++) endByException(arr); long e2 = System.currentTimeMillis(); System.out.println("endByException time:"+(e2-s2)+"ms" ); } // traverse the arr array: private static void endByException(int[] arr) { try { int i=0; while (true) { arr[i]=0; i++; //System.out.println("endByRange: arr["+i+"]="+arr[i]); } } catch (IndexOutOfBoundsException e) { } } //Transfer the arr array: private static void endByRange(int[] arr) { for (int i=0; i<arr.length; i++) { arr[i]=0; //System.out.println("endByException: arr["+i+"]="+arr[i]); } }} Running results:
endByRange time:8msendByException time:16ms
The result shows that the speed of traversing an exception is much slower than traversing an array in the ordinary way!
Article 2: Use checked exceptions for recoverable conditions, and use runtime exceptions for program errors.
| abnormal | illustrate |
|---|---|
| Runtime exception | The RuntimeException class and its subclasses are called runtime exceptions. |
| The checked exception | Exception class itself, as well as other subclasses in Exception except "runtime exception" are all checked exceptions. |
The difference is that the Java compiler checks for "checked exceptions" and does not check for "runtime exceptions".
That is to say, for the checked exception, it is either declared and thrown through throws, or captured through try-catch, otherwise it cannot be compiled. For runtime exceptions, if they are "not thrown through throws declaration" or "not caught with try-catch statement", they will still be compiled and passed. Of course, although the Java compiler does not check for runtime exceptions, we can also explain the exception through throws, or catch it through try-catch.
RithmeticException (for example, divisor is 0), IndexOutOfBoundsException (for example, array out of bounds), etc. are all runtime exceptions. For this exception, we should avoid it by modifying the code. For the checked exception, the program can be restored to run through processing. For example, suppose that because a user does not store a sufficient number of calls, he will fail when attempting to make a call on a pay call; thus throwing a checked exception.
Article 3: Avoid unnecessary use of checked exceptions
"Censored exception" is a good feature of Java. Unlike the return code, "checked exceptions" force the programmer to deal with exception conditions, greatly improving the reliability of the program.
However, excessive use of checked exceptions can make the API very inconvenient. If a method throws one or more checked exceptions, the code that calls the method must handle these exceptions in one or more catch statement blocks, or they must be thrown through throws declaration. Whether it is handled through catch or thrown through throws declarations, it adds an unnegligible burden to programmers.
Two conditions must be met for "checked exceptions": First, even if the API is used correctly, it cannot prevent the occurrence of exception conditions. Second, once an exception is generated, programmers using the API can take useful actions to process the program.
Article 4: Try to use standard exceptions
Code reuse is worthy of advocacy, this is a common rule, and exceptions are no exception. There are several benefits to reusing existing exceptions:
First, it makes your API easier to learn and use because it is consistent with the idioms that programmers have become familiar with.
Second, for programs that use these APIs, they are better readable because they are not filled with exceptions that are not familiar to programmers.
Third, the fewer exception classes, the smaller the memory usage, and the smaller the time spent on reprinting these classes.
Several of the Java standard exceptions are often used exceptions. The following table:
| abnormal | Use occasions |
|---|---|
| IllegalArgumentException | The parameter value is not appropriate |
| IllegalStateException | The parameters are not inappropriate |
| NullPointerException | When null is disabled, the parameter value is null |
| IndexOutOfBoundsException | Subscript crosses the boundary |
| ConcurrentModificationException | When concurrent modification is prohibited, the object detects concurrent modification |
| UnsupportedOperationException | Methods that object does not support customer requests |
Although they are the most commonly reused exceptions of Java platform libraries to date, other exceptions can also be reused under license conditions. For example, if you want to implement arithmetic objects such as complex numbers or matrices, it would be very appropriate to reuse ArithmeticException and NumberFormatException. If an exception meets your needs, don't hesitate to use it, but you must make sure that the condition of throwing the exception is consistent with the conditions described in the documentation for the exception. This reuse must be based on semantics, not on the name!
Finally, be sure to be clear that there is no rule that must be followed when choosing which exception to reuse. For example, considering the case of a card object, suppose there is a method for dealing operations, and its parameter (handSize) is the number of cards to be given in a hand. Suppose the caller passes a value in this parameter greater than the remaining number of cards for the entire deck. Then this situation can be interpreted as an IllegalArgumentException (the value of handSize is too large) or an IllegalStateException (the card object has too few cards relative to the client's request).
Article 5: The thrown exception should be suitable for the corresponding abstraction
This situation can be overwhelming if an exception thrown by a method has no obvious correlation with the task it performs. This often happens when a method passes an exception thrown by a low-level abstraction. When this happens, it not only confuses, but also "polluts" high-level APIs.
To avoid this problem, the high-level implementation should catch the low-level exception and throw an exception that can be introduced according to the high-level abstraction. This practice is called "exception translation".
For example, the Java collection framework AbstractSequentialList get() method is as follows (based on JDK1.7.0_40):
public E get(int index) { try { return listIterator(index).next(); } catch (NoSuchElementException exc) { throw new IndexOutOfBoundsException("Index: "+index); }}listIterator(index) will return the ListIterator object. Calling the next() method of the object may throw a NoSuchElementException exception. In the get() method, throwing a NoSuchElementException exception will be confusing. So, get() captures NoSuchElementException and throws an IndexOutOfBoundsException exception. That is, it is equivalent to converting NoSuchElementException into IndexOutOfBoundsException exception.
Article 6: The exception thrown by each method must be documented
To declare the checked exception separately, and use the @throws tag of Javadoc to accurately record the conditions for each exception to be thrown.
If many methods in a class throw the same exception for the same reason, it is acceptable to do documentation for this exception in the document comments of that class, rather than documenting individually for each method.
Article 7: Include failure in detail message-Capture message
In short, when we customize or throw an exception, we should include information related to failure.
When a program fails due to an uncaught exception, the system will automatically print out the stack trace of the exception. Contains a string representation of the exception in the stack track. Typically it contains the class name of the exception class, along with the detailed message that follows.
Article 8: Strive to maintain atomicity in failure
When an object throws an exception, we always expect that the object remains in a well-defined available state. This is especially important for the checked exception, because the caller usually expects to recover from the checked exception.
Generally speaking, a failed method call should keep the object in "its state before it was called". Methods with such attributes are called "failure atomic". It can be understood that failure still maintains atomicity. There are several ways for an object to maintain "failed atomicity":
(1) Design a non-mutable object.
(2) For methods that perform operations on mutable objects, the most common way to obtain "failed atomicity" is to check the validity of the parameters before performing the operation. As follows (pop method in Stack.java):
public Object pop() { if (size==0) throw new EmptyStackException(); Object result = elements[--size]; elements[size] = null; return result;} (3) Similar to the previous method, the calculation processing can be adjusted so that any possible failure of the calculation part occurs before the object state is modified.
(4) Write a recovery code to explain the failures during the operation and to make the object roll back to the state before the operation began.
(5) Perform an operation on a temporary copy of the object, and after the operation is completed, copy the result in the temporary copy to the original object.
While "maintaining the failed atomicity of an object" is the desired goal, it is not always possible. For example, if multiple threads attempt to access an object concurrently without proper synchronization mechanisms, the object may be left in an inconsistent state.
Even in situations where "failed atomicity" can be achieved, it is not always expected. For some operations, it can significantly increase overhead or complexity.
The general rule is: as part of the method specification, no exception should change the state of the object before calling the method. If this rule is violated, the API documentation should clearly indicate what state the object will be in.
Article 9: Don't ignore exceptions
When an API designer declares that a method will throw an exception, they are trying to illustrate something. So, please don't ignore it! The code to ignore exceptions is as follows:
try { ...} catch (SomeException e) {} An empty catch block will make the exception fail to achieve its due purpose. The purpose of the exception is to force you to deal with abnormal conditions. Ignoring an exception is like ignoring a fire alarm signal - if the fire alarm signal is turned off, then no one will see the fire alarm signal when the real fire occurs. So, at least the catch block should contain a description that explains why it is appropriate to ignore this exception.