[Java - Detailed explanation of exception mechanism]


Java exceptions are a consistent mechanism provided by Java to identify and respond to errors. The Java exception mechanism can separate the exception handling code and normal business code in the program, ensuring that the program code is more elegant and improving the robustness of the program. After integrating multiple articles, this article summarizes the relevant knowledge of Java exceptions, hoping to improve your cognitive efficiency of exceptions in Java.

 Exceptional hierarchy

Abnormalities refer to various unexpected situations, such as: file cannot be found, network connection failure, illegal parameters, etc. An exception is an event that occurs during program execution and interferes with the normal flow of instructions. Java describes various exceptions through numerous subclasses of the Throwable class in the API. Thus, Java exceptions are objects, instances of a Throwable subclass, that describe error conditions that occur in a piece of coding. Error will throw an exception when the condition is generated.

 Throwable

Throwable is the superclass of all errors and exceptions in the Java language.

Throwable contains two subclasses: Error and Exception, which are generally used to indicate that an exception has occurred.

Throwable contains a snapshot of the thread's execution stack when its thread was created, and it provides interfaces such as printStackTrace() to obtain stack trace data and other information.

 Error

Error class and its subclasses: Errors that cannot be handled in the program, indicating that a serious error has occurred in the running application.

This type of error generally indicates a problem with the JVM while the code is running. Usually there are Virtual MachineError (virtual machine running error), NoClassDefFoundError (class definition error), etc. For example, OutOfMemoryError: out of memory error; StackOverflowError: stack overflow error. When such an error occurs, the JVM terminates the thread.

These errors are unchecked exceptions, non-coding errors. Therefore, applications should not handle such errors when they occur. According to Java convention, we should not implement any new Error subclasses!

 Exception

Exceptions that the program itself can catch and handle. Exception This exception is divided into two categories: run-time exception and compile-time exception.

  • runtime exception

They are all exceptions of the RuntimeException class and its subclasses, such as NullPointerException (null pointer exception), IndexOutOfBoundsException (subscript out-of-bounds exception), etc. These exceptions are unchecked exceptions, and you can choose to capture or not process them in the program. These exceptions are generally caused by program logic errors, and the program should try to avoid the occurrence of such exceptions from a logical perspective.

The characteristic of runtime exceptions is that the Java compiler will not check it. That is to say, when this type of exception may occur in the program, even if it is not caught with a try-catch statement or declared to be thrown with a throws clause, it will Compiled and passed.

  • Non-runtime exception (Compilation exception)

They are exceptions other than RuntimeException, and all types belong to the Exception class and its subclasses. From the perspective of program syntax, it is an exception that must be handled. If it is not handled, the program will not be compiled. Such as IOException, SQLException, etc. and user-defined Exceptions. Generally, no custom checked exceptions are required.

 Checked exceptions and unchecked exceptions

  • Checkable exception (Exception that the compiler requires to be handled):

When a correct program is running, it is easy for abnormal situations to occur that are reasonable and acceptable. Although checkable exceptions are abnormal situations, their occurrence can be expected to a certain extent, and once such abnormal situations occur, they must be handled in some way.

Except for RuntimeException and its subclasses, other Exception classes and their subclasses are checkable exceptions. The characteristic of this kind of exception is that the Java compiler will check it. That is to say, when this kind of exception may occur in the program, either use a try-catch statement to catch it, or use a throws clause to declare it, otherwise the compilation will not pass. .

  • Uncheckable exception(Exception that the compiler does not require forced handling)

Including runtime exceptions (RuntimeException and its subclasses) and errors (Error).

 Abnormal basis

hint

Next we look at the basics of using exceptions.

 Exception keyword

  • try – used for listening. Place the code to be monitored (code that may throw exceptions) within the try statement block. When an exception occurs within the try statement block, the exception is thrown.
  • catch – used to catch exceptions. catch is used to catch exceptions that occur in the try statement block.
  • finally – The finally block will always be executed. It is mainly used to recycle physical resources (such as database connections, network connections, and disk files) opened in try blocks. Only the finally block, after execution is completed, will come back to execute the return or throw statement in the try or catch block. If a statement such as return or throw is used in the finally block, it will not jump back to execution and stop directly.
  • throw – used to throw exceptions.
  • throws – used in method signatures to declare exceptions that may be thrown by the method.

 Exception declaration (throws)

In Java, the currently executed statement must belong to a certain method, and the Java interpreter calls the main method to start executing the program. If there is a checked exception in the method, if it is not caught, the exception must be explicitly declared in the method header to inform the method caller that this method has an exception and needs to be handled. To declare an exception in a method, use the keyword throws in the method header, followed by the exception to be declared. If multiple exceptions are declared, separate them with commas. As follows:

public static void method() throws IOException, FileNotFoundException{
    //something statements
}

Note: If the method of the parent class does not declare an exception, the subclass cannot declare an exception after inheriting the method.

In general, you should catch exceptions that you know how to handle and pass on exceptions that you don't know how to handle. To pass exceptions, you can use the throws keyword in the method signature to declare exceptions that may be thrown.

private static void readFile(String filePath) throws IOException {
    File file = new File(filePath);
    String result;
    BufferedReader reader = new BufferedReader(new FileReader(file));
    while((result = reader.readLine())!=null) {
        System.out.println(result);
    }
    reader.close();
}

Throws rules for throwing exceptions:

  • If it is an unchecked exception, that is, Error, RuntimeException or their subclasses, you can declare the exception to be thrown without using the throws keyword. The compilation will still pass smoothly, but it will be thrown by the system at runtime. .
  • Any checked exceptions that the method can throw must be declared. That is, if a checkable exception may occur in a method, it must either be caught with a try-catch statement or thrown using a throws clause statement, otherwise it will cause a compilation error.
  • Only if an exception is thrown, the caller of the method must handle or rethrow the exception. When the caller of the method is unable to handle the exception, it should continue to throw it instead of swallowing the exception.
  • Calling methods must follow the handling and declaration rules for any checkable exceptions. If you override a method, you cannot declare a different exception than the overridden method. Any exception declared must be of the same class or subclass as the exception declared by the overridden method.

 Exception throw (throw)

If your code may cause some kind of error, you can create an instance of the appropriate exception class and throw it, which is called an exception. As follows:

public static double method(int value) {
    if(value == 0) {
        throw new ArithmeticException("参数不能为0"); //抛出一个运行时异常
    }
    return 5.0 / value;
}

In most cases, there is no need to manually throw exceptions, because most methods in Java either already handle exceptions or declare exceptions. Therefore, exceptions are generally caught or thrown up.

Sometimes we throw an exception from a catch in order to change the type of the exception. It is mostly used in multi-system integration. When a certain subsystem fails, there may be multiple exception types. A unified exception type can be used to expose it to the outside without exposing too many internal exception details.

private static void readFile(String filePath) throws MyException {    
    try {
        // code
    } catch (IOException e) {
        MyException ex = new MyException("read file failed.");
        ex.initCause(e);
        throw ex;
    }
}

 Unusual customization

Traditionally, defining an exception class should contain two constructors, a no-argument constructor and a constructor with detailed description information (the toString method of Throwable will print these detailed information, which is very useful during debugging), such as the one used above Custom MyException:

public class MyException extends Exception {
    public MyException(){ }
    public MyException(String msg){
        super(msg);
    }
    // ...
}

 Exception catching

Exception catching and handling methods usually include:

  • try-catch
  • try-catch-finally
  • try-finally
  • try-with-resource
 try-catch

Multiple exception types can be caught in a try-catch statement block and different types of exceptions can be handled differently.

private static void readFile(String filePath) {
    try {
        // code
    } catch (FileNotFoundException e) {
        // handle FileNotFoundException
    } catch (IOException e){
        // handle IOException
    }
}

The same catch can also catch multiple types of exceptions, separated by |

private static void readFile(String filePath) {
    try {
        // code
    } catch (FileNotFoundException | UnknownHostException e) {
        // handle FileNotFoundException or UnknownHostException
    } catch (IOException e){
        // handle IOException
    }
}
 try-catch-finally
  • regular syntax
try {                        
    //执行程序代码,可能会出现异常                 
} catch(Exception e) {   
    //捕获异常并处理   
} finally {
    //必执行的代码
}
  • order of execution
    • When try does not catch an exception: the statements in the try statement block are executed one by one, and the program will skip the catch statement block and execute the finally statement block and subsequent statements;
    • When try catches an exception and the catch statement block does not handle the exception: When an exception occurs in a statement in the try statement block and there is no catch statement block to handle the exception, the exception will be thrown to the JVM for processing. The statements in the finally statement block will still be executed, but the statements after the finally statement block will not be executed;
    • When try catches an exception, there is a situation in the catch statement block to handle the exception: the try statement block is executed in order. When an exception occurs when a certain statement is executed, the program will jump to the catch statement block and communicate with the catch statement. The statement blocks are matched one by one to find the corresponding handler. Other catch statement blocks will not be executed. In the try statement block, the statements after an exception will not be executed. After the catch statement block is executed, finally is executed. The statements in the statement block are finally executed after the finally statement block;
  • a complete example
private static void readFile(String filePath) throws MyException {
    File file = new File(filePath);
    String result;
    BufferedReader reader = null;
    try {
        reader = new BufferedReader(new FileReader(file));
        while((result = reader.readLine())!=null) {
            System.out.println(result);
        }
    } catch (IOException e) {
        System.out.println("readFile method catch block.");
        MyException ex = new MyException("read file failed.");
        ex.initCause(e);
        throw ex;
    } finally {
        System.out.println("readFile method finally block.");
        if (null != reader) {
            try {
                reader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
 try-finally

Can I use try-finally directly? Can.

If an exception occurs in the try block, the statements after the exception code will no longer be executed, and the finally statement will be executed directly. If the try block does not throw an exception, the finally statement will be executed after the try block is executed.

try-finally can be used in code that does not need to catch exceptions to ensure that the resource is closed after use. For example, after executing the corresponding operation in the IO stream, close the corresponding resource; use the Lock object to ensure thread synchronization, and finally ensure that the lock will be released; when connecting to the database code, close the connection operation, etc.

//以Lock加锁为例,演示try-finally
ReentrantLock lock = new ReentrantLock();
try {
    //需要加锁的代码
} finally {
    lock.unlock(); //保证锁一定被释放
}

finally will not be executed when it encounters the following situations

  • System.exit() is used in the previous code to exit the program.
  • An exception occurred in the finally statement block.
  • The thread where the program is located dies.
  • Turn off the CPU.
 try-with-resource

try-with-resource was introduced in Java 7 and is easily overlooked.

In the above example, the close method in finally may also throw IOException, thus covering the original exception. JAVA 7 provides a more elegant way to automatically release resources. The automatically released resources need to be classes that implement the AutoCloseable interface.

  • Code
private  static void tryWithResourceTest(){
    try (Scanner scanner = new Scanner(new FileInputStream("c:/abc"),"UTF-8")){
        // code
    } catch (IOException e){
        // handle exception
    }
}
  • Take a look at Scanner
public final class Scanner implements Iterator<String>, Closeable {
  // ...
}
public interface Closeable extends AutoCloseable {
    public void close() throws IOException;
}

When the try code block exits, the scanner.close method will be automatically called. The difference from placing the scanner.close method in the finally code block is that if scanner.close throws an exception, it will be suppressed and the original exception will still be thrown. . Suppressed exceptions will be added to the original exceptions by the addSusppressed method. If you want to get the suppressed exception list, you can call the getSuppressed method to get it.

 Abnormal basic summary

  • Try, catch and finally cannot be used alone, they can only be try-catch, try-finally or try-catch-finally.
  • The try statement block monitors the code, stops executing the following code when an exception occurs, and then hands the exception to the catch statement block for processing.
  • The code in the finally statement block will definitely be executed and is often used to recycle resources.
  • throws: declare an exception to notify the method caller.
  • throw: Throws an exception. It has nothing to do with whether the exception is caught or continues to be thrown.

A summary of exceptions in the book Java Programming Thoughts.

  • Address issues at the appropriate level. (Catch exceptions when you know how to handle them.)
  • Resolve the problem and re-invoke the method that generated the exception.
  • Do a little patching and continue execution, bypassing where the exception occurred.
  • Calculations are performed using other data in place of the value expected to be returned by the method.
  • Try to do everything that can be done in the current running environment, and then rethrow the same exception to a higher level.
  • Try to do everything that can be done in the current running environment, and then throw different exceptions to higher levels.
  • Terminate the program.
  • Simplify (if your exception pattern makes the problem too complex, it will be a pain to use).
  • Make libraries and programs safer.

 Commonly used exceptions

Java provides some exceptions to describe frequently occurring errors. Some of these exceptions require programmers to catch them or declare them to be thrown, and some are automatically caught and handled by the Java virtual machine. Common exception classes in Java:

  • RuntimeException

    • java.lang.ArrayIndexOutOfBoundsException Array index out of bounds exception. Thrown when the index into the array is negative or greater than or equal to the array size.
    • java.lang.ArithmeticException Arithmetic condition exception. For example: integer division by zero, etc.
    • java.lang.NullPointerException Null pointer exception. This exception is thrown when the application attempts to use null where an object is required. For example: calling the instance method of the null object, accessing the properties of the null object, calculating the length of the null object, using the throw statement to throw null, etc.
    • java.lang.ClassNotFoundException Class exception not found. This exception is thrown when the application attempts to construct a class based on a class name in string form, but cannot find the class file with the corresponding name after traversing the CLASSPAH.
    • java.lang.NegativeArraySizeException array length is negative exception
    • java.lang.ArrayStoreException Exception thrown when the array contains incompatible values
    • java.lang.SecurityException security exception
    • java.lang.IllegalArgumentException Illegal parameter exception
  • IOException

    • IOException: Exceptions that may occur when operating input streams and output streams.
    • EOFException File ended exception
    • FileNotFoundException File not found exception
  • other

    • ClassCastException type conversion exception class
    • ArrayStoreException An exception thrown when the array contains incompatible values.
    • SQLException operation database exception class
    • NoSuchFieldException field not found exception
    • NoSuchMethodException The method was not found and the exception thrown
    • NumberFormatException Exception thrown when converting a string to a number
    • StringIndexOutOfBoundsException Exception thrown by string index out of range
    • IllegalAccessException does not allow access to a certain type of exception
    • InstantiationException This exception is thrown when the application attempts to use the newInstance() method in the Class class to create an instance of a class, but the specified class object cannot be instantiated.

 Abnormal practice

hint

Handling exceptions in Java is not a simple matter. It is not only difficult for beginners to understand, but even some experienced developers need to spend a lot of time thinking about how to handle exceptions, including which exceptions need to be handled, how to handle them, etc. This is why most development teams will develop some rules to standardize exception handling.

There are many different situations to consider when you throw or catch exceptions, and most of them are done to improve the readability of your code or the usability of your API.

Exceptions are not only an error control mechanism, but also a communication medium. Therefore, in order to work better with colleagues, a team must develop a best practice and rules. Only in this way, team members can understand these common concepts and use it in their work.

Here are a few exception handling best practices used by many teams.

 Only use exceptions for abnormal situations

Exceptions should only be used for abnormal conditions, they should never be used for normal control flow. According to "Alibaba Manual": [Mandatory] RuntimeException exceptions defined in the Java class library that can be avoided through pre-checking should not be handled through catch, such as: NullPointerException, IndexOutOfBoundsException, etc.

For example, when parsing numbers in the form of strings, there may be digital format errors, which must not be achieved through catch Exception.

  • Code 1
if (obj != null) {
  //...
}
  • Code 2
try { 
  obj.method(); 
} catch (NullPointerException e) {
  //...
}

There are three main reasons:

  • The exception mechanism is originally designed to be used in abnormal situations, so few JVM implementations try to optimize their performance. Therefore, creating, throwing, and catching exceptions is expensive.
  • Putting the code in a try-catch return prevents the JVM from implementing certain optimizations that it might otherwise perform.
  • The standard pattern of array traversal does not result in redundant checks, and some modern JVM implementations optimize them away.

 Clean up resources in a finally block or use a try-with-resource statement

When using a resource like an InputStream that needs to be closed after use, a common mistake is to close the resource at the end of the try block.

  • Error example
public void doNotCloseResourceInTry() {
    FileInputStream inputStream = null;
    try {
        File file = new File("./tmp.txt");
        inputStream = new FileInputStream(file);
        // use the inputStream to read a file
        // do NOT do this
        inputStream.close();
    } catch (FileNotFoundException e) {
        log.error(e);
    } catch (IOException e) {
        log.error(e);
    }
}

The problem is that this code can only work normally when no exception is thrown. The code within the try block will execute normally and the resource can be closed normally. However, there are reasons for using try blocks. Generally, one or more methods are called that may throw an exception. Moreover, you may also throw an exception yourself, which means that the code may not execute to the end of the try block. part. As a result, you don't close the resource.

Therefore, you should put the cleanup code in finally, or use the try-with-resource attribute.

  • Method 1: Use finally code block

Unlike the try blocks in the previous lines, the finally block will always be executed. It will be executed regardless of whether the try code block executes successfully or after you handle the exception in the catch code block. Therefore, you can ensure that you clean up all open resources.

public void closeResourceInFinally() {
    FileInputStream inputStream = null;
    try {
        File file = new File("./tmp.txt");
        inputStream = new FileInputStream(file);
        // use the inputStream to read a file
    } catch (FileNotFoundException e) {
        log.error(e);
    } finally {
        if (inputStream != null) {
            try {
                inputStream.close();
            } catch (IOException e) {
                log.error(e);
            }
        }
    }
}
  • Method 2: Java 7’s try-with-resource syntax

If your resource implements the AutoCloseable interface, you can use this syntax. Most Java standard resources inherit this interface. When you open a resource in a try clause, the resource is automatically closed after the try block is executed or after exception handling.

public void automaticallyCloseResource() {
    File file = new File("./tmp.txt");
    try (FileInputStream inputStream = new FileInputStream(file);) {
        // use the inputStream to read a file
    } catch (FileNotFoundException e) {
        log.error(e);
    } catch (IOException e) {
        log.error(e);
    }
}

 Try to use standard exceptions

Code reuse is worth promoting. This is a general rule, and exceptions are no exception.

Reusing existing exceptions has several benefits:

  • It makes your API easier to learn and use because it is consistent with idioms that programmers are already familiar with.
  • For programs that use these APIs, they are more readable because they are not filled with exceptions that are unfamiliar to programmers.
  • The fewer exception classes, the smaller the memory footprint and the smaller the time overhead of reproducing these classes.

There are several Java standard exceptions that are frequently used. The following table:

abnormal Use occasions
IllegalArgumentException The value of the parameter is inappropriate
IllegalStateException Parameter status is inappropriate
NullPointerException The parameter value is null when null is prohibited
IndexOutOfBoundsException Subscript out of bounds
ConcurrentModificationException Object detects concurrent modifications when concurrent modifications are disabled
UnsupportedOperationException The object does not support the method requested by the client

Although they are by far the most commonly reused exceptions in the Java platform libraries, other exceptions can also be reused, given permission. For example, if you are implementing 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 conditions under which the exception is thrown are consistent with the conditions described in the exception's documentation. This reuse must be based on semantics, not names.

Finally, it's important to understand that there are no rules that must be followed when choosing which exceptions to reuse. For example, consider the case of playing card objects. Suppose there is a method for dealing cards. Its parameter (handSize) is the number of playing cards in a hand. Assume that the value passed by the caller in this parameter is greater than the remaining number of cards in the entire deck. Then this situation can be interpreted as either 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).

 Document exceptions

When an exception is declared on a method, it also needs to be documented. The purpose is to provide the caller with as much information as possible so that exceptions can be better avoided or handled.

Add the @throws declaration to the Javadoc and describe the scenario in which the exception is thrown.

/**
* Method description
* 
* @throws MyBusinessException - businuess exception description
*/
public void doSomething(String input) throws MyBusinessException {
   // ...
}

At the same time, when a MyBusinessException exception is thrown, it is necessary to describe the problem and related information as accurately as possible, so that whether it is printed to the log or in the monitoring tool, it can be easier to read, so that the specific error message can be better located. , the severity of the error, etc.

 Catch the most specific exceptions first

Most IDEs can help you implement this best practice. When you try to catch less specific exceptions first, they report an unreachable block of code.

But the problem is that only the first catch block that matches the exception will be executed. So if IllegalArgumentException is caught first, the catch block that should handle the more specific NumberFormatException is never reached, since it is a subclass of IllegalArgumentException .

The most specific exception classes are always caught first, with less specific catch blocks added to the end of the list.

You can see an example of such a try-catch statement in the code snippet below. The first catch block handles all NumberFormatException exceptions, and the second handles all IllegalArgumentException exceptions that are not NumberFormatException exceptions.

public void catchMostSpecificExceptionFirst() {
    try {
        doSomething("A message");
    } catch (NumberFormatException e) {
        log.error(e);
    } catch (IllegalArgumentException e) {
        log.error(e)
    }
}

 Don't capture the Throwable class

Throwable is the superclass for all exceptions and errors. You can use it in a catch clause, but you should never do it!

If you use Throwable in a catch clause, it will catch not only all exceptions but also all errors. The JVM throws errors indicating serious problems that should not be handled by the application. Typical examples are OutOfMemoryError or StackOverflowError. Both are caused by conditions outside the application's control and cannot be handled.

Therefore, it is best not to catch Throwable unless you are sure that you are in a special situation that can handle the error.

public void doNotCatchThrowable() {
    try {
        // do something
    } catch (Throwable t) {
        // don't do this!
    }
}

 Don’t ignore exceptions

Many times, developers are confident that an exception will not be thrown, so they write a catch block but do not do any processing or logging.

public void doNotIgnoreExceptions() {
    try {
        // do something
    } catch (NumberFormatException e) {
        // this will never happen
    }
}

But the reality is that unpredictable exceptions often occur, or it is impossible to determine whether the code here will be changed in the future (the code that prevents exceptions from being thrown is deleted). At this time, because the exception is caught, it is impossible to get enough error information to Positioning problem.

A reasonable approach is to at least log exception information.

public void logAnException() {
    try {
        // do something
    } catch (NumberFormatException e) {
        log.error("This should never happen: " + e); // see this line
    }
}

 Don't log and throw exceptions

This is probably the most overlooked best practice in this article.

It can be found that many codes and even class libraries have logic to catch exceptions, record logs, and throw them again. as follows:

try {
    new Long("xyz");
} catch (NumberFormatException e) {
    log.error(e);
    throw e;
}

This processing logic seems reasonable. But this often outputs multiple logs for the same exception. as follows:

17:44:28,945 ERROR TestExceptionHandling:65 - java.lang.NumberFormatException: For input string: "xyz"
Exception in thread "main" java.lang.NumberFormatException: For input string: "xyz"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Long.parseLong(Long.java:589)
at java.lang.Long.(Long.java:965)
at com.stackify.example.TestExceptionHandling.logAndThrowException(TestExceptionHandling.java:63)
at com.stackify.example.TestExceptionHandling.main(TestExceptionHandling.java:58)

As shown above, there is no more useful information attached to the subsequent logs. If you want to provide more useful information, you can wrap the exception as a custom exception.

public void wrapException(String input) throws MyBusinessException {
    try {
        // do something
    } catch (NumberFormatException e) {
        throw new MyBusinessException("A message that describes the error.", e);
    }
}

Therefore, only catch the exception when you want to handle it. Otherwise, just declare it in the method signature and let the caller handle it.

 Don’t throw away the original exception when wrapping it

It is a very common practice to catch standard exceptions and wrap them as custom exceptions. This allows you to add more specific exception information and perform targeted exception handling. When you do this, make sure to set the original exception as the reason (note: refer to the original exception e in NumberFormatException e in the code below). The Exception class provides a special constructor method that accepts a Throwable as a parameter. Otherwise, you will lose the stack trace and the original exception message, which will make it difficult to analyze the exception events that caused the exception.

public void wrapException(String input) throws MyBusinessException {
    try {
        // do something
    } catch (NumberFormatException e) {
        throw new MyBusinessException("A message that describes the error.", e);
    }
}

 Don’t use exceptions to control program flow

Exceptions should not be used to control the execution flow of the application. For example, if you should use if statements for conditional judgment, you use exception handling instead. This is a very bad habit and will seriously affect the performance of the application.

 Don't use return in finally blocks.

After the return statement in the try block is successfully executed, it does not return immediately, but continues to execute the statement in the finally block. If there is a return statement here, it returns directly here, ruthlessly discarding the return point in the try block.

Here is a counterexample:

private int x = 0;
public int checkReturn() {
    try {
        // x等于1,此处不返回
        return ++x;
    } finally {
        // 返回的结果是2
        return ++x;
    }
}

 Deep understanding of exceptions

hint

Let’s take a deeper look at exceptions and look at the underlying implementation.

 JVM’s mechanism for handling exceptions?

When it comes to the JVM exception handling mechanism, we need to mention the Exception Table, hereafter referred to as the exception table. We are not in a hurry to introduce the exception table for the time being. Let's first look at a simple example of Java handling exceptions.

public static void simpleTryCatch() {
   try {
       testNPE();
   } catch (Exception e) {
       e.printStackTrace();
   }
}

The above code is a very simple example, used to catch and handle a potential null pointer exception.

Of course, if we just look at the simple code, it is difficult to see what is profound, and there is nothing to talk about in today's article.

So here we need to use a magic weapon, which is javap, a tool used to disassemble class files, which is provided by JDK like javac.

Then we use javap to analyze this code (need to compile with javac first)

//javap -c Main
 public static void simpleTryCatch();
    Code:
       0: invokestatic  #3                  // Method testNPE:()V
       3: goto          11
       6: astore_0
       7: aload_0
       8: invokevirtual #5                  // Method java/lang/Exception.printStackTrace:()V
      11: return
    Exception table:
       from    to  target type
           0     3     6   Class java/lang/Exception

When you see the above code, you should have a knowing smile, because you finally see the Exception table, which is the exception table we want to study.

The exception table contains information about one or more exception handlers (Exception Handler). This information includes the following

  • from The starting point where exceptions may occur
  • to End point where exception may occur
  • target The location of the exception handler after the exception occurred before the above from and to
  • type Class information of the exception handled by the exception handler

So when is the exception table used?

The answer is when an exception occurs, when an exception occurs

  • 1. The JVM will search the exception table in the method where the exception currently occurs to see if there is a suitable handler to handle it.
  • 2. If the current method exception table is not empty, and the exception matches the handler's from and to nodes, and the type also matches, the JVM calls the caller located in the target to handle it.
  • 3. If no reasonable handler is found for the previous entry, continue to search for the remaining entries in the exception table.
  • 4. If the exception table of the current method cannot be handled, search upward (stack popping processing) where the method was just called, and repeat the above operation.
  • 5. If all stack frames are popped and are still not processed, they will be thrown to the current Thread, and the Thread will terminate.
  • 6. If the current Thread is the last non-daemon thread and the exception is not handled, it will cause the JVM to terminate.

The above are some of the mechanisms by which the JVM handles exceptions.

try catch -finally

In addition to simple try-catch, we often use it in combination with finally. For example, code like this

public static void simpleTryCatchFinally() {
   try {
       testNPE();
   } catch (Exception e) {
       e.printStackTrace();
   } finally {
       System.out.println("Finally");
   }
}

Similarly, we use javap to analyze the code

public static void simpleTryCatchFinally();
    Code:
       0: invokestatic  #3                  // Method testNPE:()V
       3: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
       6: ldc           #7                  // String Finally
       8: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      11: goto          41
      14: astore_0
      15: aload_0
      16: invokevirtual #5                  // Method java/lang/Exception.printStackTrace:()V
      19: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      22: ldc           #7                  // String Finally
      24: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      27: goto          41
      30: astore_1
      31: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      34: ldc           #7                  // String Finally
      36: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      39: aload_1
      40: athrow
      41: return
    Exception table:
       from    to  target type
           0     3    14   Class java/lang/Exception
           0     3    30   any
          14    19    30   any

Different from before, this time there are three pieces of data in the exception table, and we only captured one Exception. The type of the last two items in the exception table is any; the meaning of the above three exception table items is:

  • If an Exception type exception occurs between 0 and 3, the exception handler at position 14 is called.
  • If between 0 and 3, no matter what exception occurs, the handler at position 30 is called.
  • If it is between 14 and 19 (that is, the catch part), no matter what exception occurs, the handler at position 30 will be called.

Analyzing the above Java code again, the part in finally has been extracted into the try part and the catch part. Let’s adjust the code again and take a look

public static void simpleTryCatchFinally();
    Code:
      //try 部分提取finally代码,如果没有异常发生,则执行输出finally操作,直至goto到41位置,执行返回操作。  

       0: invokestatic  #3                  // Method testNPE:()V
       3: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
       6: ldc           #7                  // String Finally
       8: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      11: goto          41

      //catch部分提取finally代码,如果没有异常发生,则执行输出finally操作,直至执行got到41位置,执行返回操作。
      14: astore_0
      15: aload_0
      16: invokevirtual #5                  // Method java/lang/Exception.printStackTrace:()V
      19: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      22: ldc           #7                  // String Finally
      24: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      27: goto          41
      //finally部分的代码如果被调用,有可能是try部分,也有可能是catch部分发生异常。
      30: astore_1
      31: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      34: ldc           #7                  // String Finally
      36: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      39: aload_1
      40: athrow     //如果异常没有被catch捕获,而是到了这里,执行完finally的语句后,仍然要把这个异常抛出去,传递给调用处。
      41: return

Catch sequence issue

The order of our catches in the code determines the position of the exception handler in the exception table. Therefore, the more specific exceptions must be handled first, otherwise the following problems will occur.

private static void misuseCatchException() {
   try {
       testNPE();
   } catch (Throwable t) {
       t.printStackTrace();
   } catch (Exception e) { //error occurs during compilings with tips Exception Java.lang.Exception has already benn caught.
       e.printStackTrace();
   }
}

This code will cause compilation failure, because catching Throwable first and then Exception will cause the subsequent catch to never be executed.

Return and finally issues

This is a relatively extreme problem in our expansion. If code like this contains both return and finally, will finally be executed?

public static String tryCatchReturn() {
   try {
       testNPE();
       return  "OK";
   } catch (Exception e) {
       return "ERROR";
   } finally {
       System.out.println("tryCatchReturn");
   }
}

The answer is that finally will be executed, so let’s use the above method. Let’s take a look at why finally will be executed.

public static java.lang.String tryCatchReturn();
    Code:
       0: invokestatic  #3                  // Method testNPE:()V
       3: ldc           #6                  // String OK
       5: astore_0
       6: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
       9: ldc           #8                  // String tryCatchReturn
      11: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      14: aload_0
      15: areturn       返回OK字符串,areturn意思为return a reference from a method
      16: astore_0
      17: ldc           #10                 // String ERROR
      19: astore_1
      20: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
      23: ldc           #8                  // String tryCatchReturn
      25: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      28: aload_1
      29: areturn  //返回ERROR字符串
      30: astore_2
      31: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
      34: ldc           #8                  // String tryCatchReturn
      36: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      39: aload_2
      40: athrow  如果catch有未处理的异常,抛出去。

 Are exceptions time-consuming? Why does it take time?

Speaking of using it abnormally slowly, let’s first take a look at where it is abnormally slow? How slow? The following test case simply tests the time-consuming comparison of creating objects, creating exception objects, and throwing and catching exception objects:

public class ExceptionTest {  
  
    private int testTimes;  
  
    public ExceptionTest(int testTimes) {  
        this.testTimes = testTimes;  
    }  
  
    public void newObject() {  
        long l = System.nanoTime();  
        for (int i = 0; i < testTimes; i++) {  
            new Object();  
        }  
        System.out.println("建立对象:" + (System.nanoTime() - l));  
    }  
  
    public void newException() {  
        long l = System.nanoTime();  
        for (int i = 0; i < testTimes; i++) {  
            new Exception();  
        }  
        System.out.println("建立异常对象:" + (System.nanoTime() - l));  
    }  
  
    public void catchException() {  
        long l = System.nanoTime();  
        for (int i = 0; i < testTimes; i++) {  
            try {  
                throw new Exception();  
            } catch (Exception e) {  
            }  
        }  
        System.out.println("建立、抛出并接住异常对象:" + (System.nanoTime() - l));  
    }  
  
    public static void main(String[] args) {  
        ExceptionTest test = new ExceptionTest(10000);  
        test.newObject();  
        test.newException();  
        test.catchException();  
    }  
}  

operation result:

建立对象:575817  
建立异常对象:9589080  
建立、抛出并接住异常对象:47394475  

Creating an exception object takes about 20 times as long as creating an ordinary Object (actually the difference will be larger than this number, because the loop also takes up time. Readers who pursue accuracy can measure the time consuming of an empty loop and then Subtract this part before comparison), and the time spent on throwing and catching an exception object is about 4 times that of creating an exception object.


Guess you like

Origin blog.csdn.net/abclyq/article/details/134700543