Exceptions are coming, exceptions are caught, exceptions are thrown, custom exceptions

1. Catch the exception:

        In Java, any statement that may throw an exception can be caught with try...catch. Put the statements that may cause exceptions in try{...}, and then use catch to catch the corresponding Exception and its subclasses.

        1. Multiple catch statements

                Multiple catch statements can be used, and each catch catches the corresponding Exception and its subclasses. After the JVM catches an exception, it matches the catch statement from top to bottom. After matching a certain catch, it executes the catch code block, and then does not continue to match. Simply put, only one of multiple catch statements can be executed. E.g:

public static void main(String[] args) {
    try {
        process1();
        process2();
        process3();
    } catch (IOException e) {
        System.out.println(e);
    } catch (NumberFormatException e) {
        System.out.println(e);
    }
}

                When there are multiple catches, the order of the catches is very important, and the subclass must be written first, for example:

public static void main(String[] args) {
    try {
        process1();
        process2();
        process3();
    } catch (IOException e) {
        System.out.println("IO error");
    } catch (UnsupportedEncodingException e) { // 永远捕获不到
        System.out.println("Bad encoding");
    }
}

                For the above code, UnsupportedEncodingException can never be caught because it is a subclass of IOException. When UnsupportedEncodingException is thrown, it will be caught and executed by catch(IOException e){...}. Therefore, the correct approach should be to put the subclass field first:

public static void main(String[] args) {
    try {
        process1();
        process2();
        process3();
    } catch (UnsupportedEncodingException e) {
        System.out.println("Bad encoding");
    } catch (IOException e) {
        System.out.println("IO error");
    }
}

       

        2. finally statement 

                Whether or not there is an exception, we all want to execute some statements, such as cleanup work, we can write the execution statement several times: put the normal execution in try, and write it again for each catch, for example:

public static void main(String[] args) {
    try {
        process1();
        process2();
        process3();
        System.out.println("中国抗击疫情必将胜利!!!");    //这行代码是我们想必须执行的
    } catch (UnsupportedEncodingException e) {
        System.out.println("Bad encoding");
        System.out.println("END");
    } catch (IOException e) {
        System.out.println("IO error");
        System.out.println("END");
    }
}

                But this line of code is System.out.println("China will win the fight against the epidemic!!!"); but if the previous calling method is thrown and an exception occurs, this line of code will not be executed, Then we can use the finally block to ensure that it will be executed with or without errors, and the above code can be rewritten as:

public static void main(String[] args) {
    try {
        process1();
        process2();
        process3();
    } catch (UnsupportedEncodingException e) {
        System.out.println("Bad encoding");
    } catch (IOException e) {
        System.out.println("IO error");
    } finally {
        System.out.println("中国抗击疫情必将胜利!!!");    //这行代码是我们想必须执行的
    }
}

                Pay attention to several features of finally:

                        1. The finally statement is not necessary, it can be written or not;

                        2.finally is always executed last;

               If no exception occurs, the try{...} block is executed normally, and finally is executed. If an exception occurs, the execution of the try{...} block is interrupted, then the execution of the matching catch block is jumped, and finally finally is executed. It can be seen that finally is used to ensure that some code must be executed.

                In some cases, we can also use the try...finally structure without catch, for example:

void process(String file) throws IOException {
    try {
        ...
    } finally {
        System.out.println("中国抗击疫情必将胜利!!!");    //这行代码是我们想必须执行的
    }
}

        3. Catch a variety of exceptions:

                If the processing logic of some exceptions is the same, but there is no inheritance relationship before the exception itself, you need to write multiple catch statements, for example:

public static void main(String[] args) {
    try {
        process1();
        process2();
        process3();
    } catch (IOException e) {
        System.out.println("Bad input");
    } catch (NumberFormatException e) {
        System.out.println("Bad input");
    } catch (Exception e) {
        System.out.println("Unknown error");
    }
}

                Because the code for handling IOException and NumberFormatException is the same, we can combine her dual use | together, for example:

public static void main(String[] args) {
    try {
        process1();
        process2();
        process3();
    } catch (IOException | NumberFormatException e) { // IOException或NumberFormatException
        System.out.println("Bad input");
    } catch (Exception e) {
        System.out.println("Unknown error");
    }
}

                Use try...catch...finally to catch exception summary:

                        1. The matching order of multiple catch statements is very important, and subclasses must be placed first.

                        2. The finally statement ensures that it will be executed with or without exception, and it is optional.

                        3. A catch statement can also match multiple non-inheritance exceptions.

2. Throws an exception:

        1. Abnormal propagation route:

                When a method throws an exception, if the current method does not catch the exception, the exception will be thrown to the upper-level calling method until a try...catch is caught:

public class Main {
    public static void main(String[] args) {
        try {
            process1();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static void process1() {
        process2();
    }

    static void process2() {
        Integer.parseInt(null); // 会抛出NumberFormatException
    }
}

                The call stack of the method can be printed out through printStackTrace(), for example:

java.lang.NumberFormatException: null
    at java.base/java.lang.Integer.parseInt(Integer.java:614)
    at java.base/java.lang.Integer.parseInt(Integer.java:770)
    at Main.process2(Main.java:16)
    at Main.process1(Main.java:12)
    at Main.main(Main.java:5)

                printStackTrace() is very useful for debugging errors. The above information means: NumberFormatException is thrown in the java.lang.Integer.parseInt method. From bottom to top, the calling layer is:

                        1.main() calls process1();

                        2.process1() calls process2();

                        3.process2() calls Integer.parseInt(String);

                        4.Integer.parseInt(String)调用Integer.parseInt(String,int);

                Looking at the source code of Integer.java, we can see that the code of the throwing exception method is as follows:

public static int parseInt(String s, int radix) throws NumberFormatException {
    if (s == null) {
        throw new NumberFormatException("null");
    }
    ...
}

        2. Throw an exception

                When an error occurs, such as the user entering an illegal character, we can throw an exception. How to throw an exception? Referring to the Integer.parseInt() method above, throwing an exception is divided into two steps:

                        1. Create an instance of an Exception;

                        2. Use the throw statement to throw;

                E.g:

void process2(String s) {
    if (s==null) {
        NullPointerException e = new NullPointerException();
        throw e;
        //throw new NullPointerException();
    }
}

                In fact, most code that throws an exception will be combined into a line like the commented out above or the following:

void process2(String s) {
    if (s==null) {
        throw new NullPointerException();
    }
}

                If a method catches an exception and then throws a new exception in the catch itself, it is equivalent to converting the type of the thrown exception:

void process1(String s) {
    try {
        process2();
    } catch (NullPointerException e) {
        throw new IllegalArgumentException();
    }
}

void process2(String s) {
    if (s==null) {
        throw new NullPointerException();
    }
}

                When process2() throws NullPointerException, it is caught by process1() and then throws IllegalArgumentException(). If IllegalArgumentException is caught in main(), wine will print the exception stack to see, as follows:

public class Main {
    public static void main(String[] args) {
        try {
            process1();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static void process1() {
        try {
            process2();
        } catch (NullPointerException e) {
            throw new IllegalArgumentException();
        }
    }

    static void process2() {
        throw new NullPointerException();
    }
}

                The printed exception stack looks like:

java.lang.IllegalArgumentException
    at Main.process1(Main.java:15)
    at Main.main(Main.java:5)

                This shows that the new exception has lost the information of the original exception or the fundamental exception, and we can no longer see the information of the original exception NullPointerException. In order to trace the complete exception stack information, when constructing an exception, pass the original Exception instance in, and the new Exception can hold the original Exception information. Modify the above code as follows:

public class Main {
    public static void main(String[] args) {
        try {
            process1();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static void process1() {
        try {
            process2();
        } catch (NullPointerException e) {
            throw new IllegalArgumentException(e);
        }
    }

    static void process2() {
        throw new NullPointerException();
    }
}

                Running the above code, the printed exception stack is similar to the following:

java.lang.IllegalArgumentException: java.lang.NullPointerException
    at Main.process1(Main.java:15)
    at Main.main(Main.java:5)
Caused by: java.lang.NullPointerException
    at Main.process2(Main.java:20)
    at Main.process1(Main.java:13)

                Note : Caused by: Xxx, indicating that the caught IllegalArgumentException is not the root cause of the problem, the root cause is the NullPointerException, which was thrown in the Main.process2() method. To get the raw exception information in the code, you can use the Throwable.getCause() method. If it returns null, it means that the exception is the root exception that caused the problem. With the complete exception stack information, we can quickly locate and modify the code to solve the problem.

When catching an exception and throwing it again, be sure to keep the original exception so that the first crime scene can be found faster! ! !

                If we throw an exception in a try or catch block, will the finally block still execute? E.g:

public class Main {
    public static void main(String[] args) {
        try {
            Integer.parseInt("abc");
        } catch (Exception e) {
            System.out.println("catched");
            throw new RuntimeException(e);
        } finally {
            System.out.println("finally");
        }
    }
}

                The execution result of the above code is as follows:

catched
finally
Exception in thread "main" java.lang.RuntimeException: java.lang.NumberFormatException: For input string: "abc"
    at Main.main(Main.java:8)
Caused by: java.lang.NumberFormatException: For input string: "abc"
    at ...

                The first line prints catchd, indicating that the catch statement block is entered, and the second line prints finally, indicating that the finally statement block is executed, so we can know that when an exception is thrown in the catch, it will not affect the finally execution, JVM Will execute finally first, and then throw an exception.

        3. Exception shielding:

                If an exception is thrown when the finally statement is executed, can the catch block continue to throw? E.g:

public class Main {
    public static void main(String[] args) {
        try {
            Integer.parseInt("abc");
        } catch (Exception e) {
            System.out.println("catched");
            throw new RuntimeException(e);
        } finally {
            System.out.println("finally");
            throw new IllegalArgumentException();
        }
    }
}

                Execute the above code and find the exception information as follows:

catched
finally
Exception in thread "main" java.lang.IllegalArgumentException
    at Main.main(Main.java:11)

                The console output shows that after finally throwing an exception, the exception that was going to be thrown in the catch "disappears" because only one exception can be thrown. An exception that is not thrown is called a "masked" exception (Suppressed Exception). In rare cases, we need to know all exceptions, so how to save all exception information? The method is to first save the original field in a variable, then call Throwable.addSuppressed(), add the original field, and then throw it in finally, as follows:

public class Main {
    public static void main(String[] args) throws Exception {
        Exception origin = null;
        try {
            System.out.println(Integer.parseInt("abc"));
        } catch (Exception e) {
            origin = e;
            throw e;
        } finally {
            Exception e = new IllegalArgumentException();
            if (origin != null) {
                e.addSuppressed(origin);
            }
            throw e;
        }
    }
}

                When both catch and finally throw exceptions, although the catch exception is masked, the finally thrown exception still contains it:

Exception in thread "main" java.lang.IllegalArgumentException
    at Main.main(Main.java:11)
Suppressed: java.lang.NumberFormatException: For input string: "abc"
    at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.base/java.lang.Integer.parseInt(Integer.java:652)
    at java.base/java.lang.Integer.parseInt(Integer.java:770)
    at Main.main(Main.java:6)

                All Suppressed Exceptions can be obtained through Throwable.getSuppressed(). In the vast majority of cases, do not throw exceptions in finally. So we usually don't care about Suppressed Exception.

                

               Throwing exception summary:

                        1. Calling printStackTrace() can print the exception propagation stack, which is very effective for debugging programs;

                        2. When an exception is caught and a new exception is thrown again, the original exception information should be held;

                        3. Usually do not throw exceptions in finally. If an exception is thrown in finally, the original exception should be added to the original exception, and the caller can get all the added Suppressed Exception through Throwable.getSuppressed().

3. Custom exception:

                When we need to throw exceptions in our code, try to use the exceptions already defined by the JDK. For example: the parameter type is invalid, we should throw an IllegalArgumentException:

static void process1(int age) {
    if (age <= 0) {
        throw new IllegalArgumentException();
    }
}

                Of course, in large projects, new exception types can be customized, but it is very important to maintain a reasonable inheritance system. The usual practice is to customize an exception as a "root exception", and then derive various services. type of exception. The root exception needs to be derived from a suitable Exception, usually it is recommended to derive from RuntimeException:

public class BaseException extends RuntimeException {
}

                All other exception types can be derived from the root exception:

public class UserNotFoundException extends BaseException {
}

public class LoginFailedException extends BaseException {
}

...

                Custom root exceptions should provide multiple constructors (preferably corresponding to RuntimeException):

public class BaseException extends RuntimeException {
    public BaseException() {
        super();
    }

    public BaseException(String message, Throwable cause) {
        super(message, cause);
    }

    public BaseException(String message) {
        super(message);
    }

    public BaseException(Throwable cause) {
        super(cause);
    }
}

                Refer to the native implementation of RuntimeException in the above constructor. In this way, when an exception is thrown, the appropriate constructor can be selected.

                Custom exception summary:

                        1. When an exception is thrown, reuse the exception type already defined by the JDK as much as possible;

                        2. When customizing the exception system, it is recommended to derive the root exception from the RuntimeException class, and then derive other types of exceptions from the root exception.

                        3. When customizing exceptions, try to imitate the RuntimeException class and provide multiple construction methods.

Guess you like

Origin blog.csdn.net/chen_kai_fa/article/details/123614980