Preliminary exploration of exception handling operation in Java language (try-catch-finally)

​​​​​​Exception handling mechanism

The exception handling mechanism in Java allows the code to continue executing even if an exception occurs in the program rather than exiting the program directly . Let's first briefly understand how to use exception handling.

Before referencing exception handling , the existence of exceptions in the running code will cause the JVM to directly interrupt the program and output exception information, as follows:

public class ExceptionTry {
    public static void main(String[] args) {
        int i = 0;
        System.out.println(9 / i);
        System.out.println("程序继续执行");
    }
}

After using the exception handling mechanism, we can simply handle the exception. There are many application scenarios. If the price filtering range is implemented in the filtering of Taobao products, the following code is used (the current learning is not in-depth enough, and the implementation of Taobao's specific functions may be It’s not that simple, that’s just how I understand it):

Scanner scanner = new Scanner(System.in);
double a = scanner.nextDouble();

If the user becomes playful or accidentally touches the input string, an InputMismatchException will occur. That is, the input type cannot be converted to a double type, and the data input type does not match. This will cause the program to directly interrupt and exit, and the user will use it again. It can only be restarted, which greatly affects the software experience. At the same time, it will also cause a lot of unnecessary trouble in the debug code. At this time, using exception handling can optimize the user experience and greatly improve the robustness of the code. Still using the first example:
 

public class ExceptionTry {
    public static void main(String[] args) {
        try {
            int i = 0;
            System.out.println(9 / i);
        } catch (Exception e) {
            System.out.println("代码出现异常:" + e.getMessage());
        }
        System.out.println("程序继续执行");
    }
}

At this time, the code ends normally:

The difference between exceptions and errors

Exception and error are two completely different concepts. In my understanding, the former is a general problem caused by accidental code or programming errors and other factors. It can be dealt with using targeted code, such as Null pointer usage exceptions, arithmetic error exceptions (dividing by zero, opening negative roots, etc.), type conversion errors, array out of bounds, etc.; and errors are more serious internal errors in the JVM system or resource exhaustion that cause the program to be completely unable to execute, such as Stack overflow, insufficient memory, etc. Generally speaking, the former is stomachache and the latter is cancer.

Exceptions can be subdivided into run-time exceptions and compile-time exceptions. The former is a logical error that cannot be detected by the compiler. For example, the divide-by-zero error in the above example is a run-time exception, while the latter is required by the compiler. Handled exceptions, otherwise the compilation cannot pass, such as FileNotFundException that occurs when using an empty file when opening a file stream.
 

Use of try-catch-finally

Try-catch-finally syntax understanding

Now that we understand the role of the exception handling mechanism, we can first look at how this statement is used.

On the whole, it is easy to understand. The code after try is the code that we think may cause an exception. If an exception occurs in this code block, the JVM will capture the exception into the catch module, and then we can perform operations there. For targeted operations, for example, in the first example, we can output an error in catch and then ask for re-entering:
 

public class ExceptionTry {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int i = 0;
        try {
            System.out.println(9 / i);
        } catch (Exception e) {
            System.out.println("代码出现异常:" + e.getMessage());
            System.out.println("请重新输入i");
            i = scanner.nextInt();
        }
        System.out.println("程序继续执行");
    }
}

We can also set a loop in the outer layer to ensure that the input data will not be abnormal. If an exception occurs, the input will be repeated in the loop. We can even customize the exception later to make the input data meet our requirements (except for the exception of the system itself) In addition to the prescribed classes, you can also customize them. After all, exceptions are essentially a class).

In the event of an exception, the code is executed in the order of try-catch-finally. finally can be omitted. finally is the content of the statement that must be executed. Generally, when executing a piece of code, the business in it must be executed regardless of whether an exception occurs. In logical application scenarios, for example, no matter whether an exception occurs, some file streams need to be closed, some resources need to be closed, etc. In fact, catch can also be omitted, but in this case the program exception will be handled by the JVM as before: interrupt the program and output the exception information.
 

Specific usage examples of try-catch-finally
import java.util.Scanner;
 
public class Try {
    public static void main(String[] args) {
        while(true) {
            try {
                Scanner scanner = new Scanner(System.in);
                System.out.println("请输入一个整数");
                int a = scanner.nextInt();
                System.out.println("a的值为:" + a);
                break;
            } catch (Exception e) {
                System.out.println("输入错误,请重新输入");
            }
        }
    }
}

The above realizes the data input that only integers can be obtained during data input, and the program will not exit the data input when the input is abnormal. We can also add the conditions we want to specifically limit, such as size, bit length, etc.

One more thing, if you try to create a Scanner object outside the loop, and then still use the nextInt method internally, and input an exception for the first time during debugging, then there will be no chance for input again, and you will fall into an infinite loop, unable to achieve the functionality we want. Friends who are interested can take a look.
 

When an exception occurs, we directly jump out of the code execution. After tracing the specific original code of Scanner through breakpoints, we found that when exiting the called method, some parameters need to be reset in order not to affect the next operation. Now, Because the exception jumps out directly, the reset operation cannot be performed, which will affect the next data read. Regarding the above problem, what I discovered is that the skipped attribute in the Scanner (used to determine whether the delimiter has been skipped, which should be whether the field has been read) has not been changed back to false, so the scanner object in the subsequent loop Each use of the skipped attribute cannot be modified. (Because skipped is true every time, it means that the field has been read; the read field token is empty, and the parameter reset operation cannot be performed; needInput is initially false, and only the first normal read After readInput is also modified to false, no more input is needed, and the input stage is skipped; so an exception is thrown again and the throwFor() exit method is called to nextInt(), but the skipped attribute is not modified when exiting, so the next Once it is still true, it enters an infinite loop) The specific source code content in question is as follows:

We can update the object by creating the object in the loop body so that the scanner will be recreated every time it is called; or we can change the data type by using Integer.paseInt(scanner.next()) so that it will not be The scanner's properties were not reinitialized and suffered an exception forced interruption, because the exception occurred in the external paseInt at this time.

Of course, strictly speaking, I should actually go in and look at the getCompleteTokenInBuffer method in detail, and my explanation is not very good in my opinion, but for a little rubbish like me, the source code seems to be true, which makes me a little dizzy. The annotation of getCompleteTokenInBuffer should probably be like this, but I want to find out more specifically, because I am both good at it and fun-loving. If my strength improves in the future, I will clarify this issue in more detail. I sincerely welcome all my friends, brothers and sisters to correct the errors and shortcomings in my statement. Thank you!
 

The overall mechanism of exception handling

After knowing the usage of try-catch-finally, we can take a look at the overall processing mechanism of exception handling. We know that before using try-catch-finally, exceptions are handled by our JVM system. The JVM has the same top-level status as our Object class in exception handling. Object is the final parent class of all classes, and the JVM is also the The final unit of exception handling for the class. Specifically, it is like the structure in the figure below:

Our main calls the f1 method, and the f1 method calls the f2 method. Our throws are used here. Throws are like trying to catch a fish. If f2 does not explicitly handle this exception, then f2 will throw this exception to f1 that calls it. I don’t want to deal with this exception. I don’t care. . Therefore, before using try processing, the exception will be thrown to the most advanced JVM system layer by layer, and he will eventually handle the exception. At the same time, we can modify the exception type to selectively discard specified exceptions. (Modify Exception to a specific exception type)
 

Usage details of try-catch-finally

public class Try {
   public static void main(String[] args) {
       System.out.println(a.method());
   }
}
class a{
   public static int method() {
       int i = 0;
       try {
           return 9 / i;
       } catch (Exception e) {
           System.out.println("catch");
           return ++i;
       } finally {
           System.out.println("finally");
           return ++i;
       }
   }
}

Regarding the execution sequence of this piece of code, we only need to clarify one concept to solve it easily - the code in finally is the business logic that must be executed. In this statement, an exception occurred in try and was caught in catch. After execution, return ++i is executed; of course, after executing ++i first, it is found that finally has not been executed, so the finally statement is executed before return. , and finally return in finally. So the final result is 2;
 

If ++i in catch is replaced by i - 8, our process will not actually change, but there is a big difference between i - 8 and ++i. The latter can be executed as an independent statement, while The former has no receiver, so return i - 8; here, a temporary variable temp is actually used to store the value of i - 8 at the bottom layer, and then the finally code is executed. If the code in finally does not have an exit return, even if i is modified in finally, the value of temp will eventually be returned; if so, it will be returned directly in finally.

public class Try {
    public static void main(String[] args) {
        System.out.println(a.method());
    }
}
class a{
    public static int method() {
        int i = 0;
        try {
            return 9 / i;
        } catch (Exception e) {
            System.out.println("catch");
            return i - 8;
        } finally {
            System.out.println("finally");
            i++;
//            return ++i; //返回2
 
        }
    }
}

The result is as follows:

catch
finally
-8

Guess you like

Origin blog.csdn.net/m0_55333789/article/details/133337752