The introduction

When you think of exceptions, the first thing that comes to mind is a fixed combination like try-catch-finally. Indeed, this is the basic paradigm of Java exception handling, so let’s take a closer look at the Java exception mechanism and see what other details are missing behind it.

Introduction to Java Exceptions

What is an exception? When an Exception occurs, the system automatically generates an Exception object to inform the program to handle it accordingly. There are many types of Java exceptions, so let’s use a diagram to look at the inheritance hierarchy of Java exceptions:

Error in Java exceptions

Error generally refers to compile-time or system errors, such as virtual machine-related errors, system crashes (for example, outofMemoryErrors sometimes encountered in development), etc. Such errors cannot be recovered or caught, will interrupt the application, and usually the application cannot handle them, so no attempt should be made to catch them.

Exception in a Java Exception

As we explained above, exceptions in Java exceptions are divided into checked exceptions and runtime exceptions (unchecked exceptions). Let’s expand on that.

Checked exception in Java

When you write IO code, you must always use a try-catch on a File or Stream, otherwise the compilation will fail. This is because these are the types of exceptions that are checked. At compile time, the compiler must try for checked exceptions… Catch or throws, otherwise the compilation fails. Common checked exceptions include: IO operation, ClassNotFoundException, thread operation, etc.

Unchecked exceptions in Java (runtime exceptions)

RuntimeException and its subclasses are collectively called unchecked exceptions, such as: NullPointExecrption, a NumberFormatException (string is converted to digital), ArrayIndexOutOfBoundsException (an array), a ClassCastException, Arit (type conversion error) HmeticException (arithmetic error) etc.

Java exception handling

The general format for Java to handle exceptions looks like this:

Try {/// code that may throw an exception}catch(Type1 ID1){// code that handles Type1 id2}catch(Type2 ID2){// Code that handles Type2 id2}Copy the code

Put code in the try block that might get an exception (but we don’t know what kind). If an exception occurs, the try block throws an automatically generated exception object, and the exception handling mechanism takes care of finding the first handler whose parameters match the exception type, and then executing the catch statement (without looking down). If our catch statement does not match, the JVM will still throw an exception.

Throws keyword in Java

If the current method does not know how to handle the exception, throw the exception to the caller for processing or to the JVM using throws. The JVM handles exceptions by printing trace stack information for the exception and terminating the program. Throws throws. Throw multiple exceptions and separate them with commas (,). Here’s an example:

public void f() throws ClassNotFoundException,IOException{}
Copy the code

The f() method must be called with a catch-classNotFoundexception and IOException or the base catch-Exception class. Throws exception (throws); throws exception (throws); throws exception (throws); throws exception (throws); throws exception (throws); This approach has the advantage of reserving a place for the exception so that it can be thrown later without modifying existing code. This design is important when defining abstract classes and interfaces so that derived classes or interface implementations can throw these pre-declared exceptions.

Printing exception Information

The Exception class’s base class, Exception, provides a set of methods to get some information about the Exception. So if we get an exception object, we can print out some useful information. The most common is the void printStackTrace() method, which returns an array of elements from the stack trace, each representing a frame in the stack. Element 0 is the top of the stack and is the last method call in the call sequence (where the exception is created and thrown); It has several different overloaded versions that can output information to different streams. The following code shows how to print basic exception information:

public void f() throws IOException{
    System.out.println("Throws SimpleException from f()"); 
    throw new IOException("Crash");
 }
 public static void main(String[] agrs) {
    try {
    	new B().f();
    } catch (IOException e) {
    	System.out.println("Caught Exception");
        System.out.println("getMessage(): "+e.getMessage());
        System.out.println("getLocalizedMessage(): "+e.getLocalizedMessage());
        System.out.println("toString(): "+e.toString());
        System.out.println("printStackTrace(): "); e.printStackTrace(System.out); }}Copy the code

Let’s look at the output:

Throws SimpleException from f()
Caught  Exception
getMessage(): Crash
getLocalizedMessage(): Crash
toString(): java.io.IOException: Crash
printStackTrace(): 
java.io.IOException: Crash
	at com.learn.example.B.f(RunMain.java:19)
	at com.learn.example.RunMain.main(RunMain.java:26)
Copy the code

Use finally for cleanup

The reason for introducing the finally statement is that we expect some code to always execute, whether or not an exception is thrown in the try block. The basic format for exception handling becomes the following:

Try {// code that may throw an exception} catch(Type1 ID1){// code that handles Type1 id2} catch(Type2 id2){// Code that handles Type2 id2} finally{// Code that always executes}Copy the code

Finally statement used in Java when resources other than memory are expected to return to their initial state. Such as open files or network connections, images drawn on the screen, etc. Here’s a case study:

public class FinallyException {
    static int count = 0;

    public static void main(String[] args) {
        while (true){
            try {
                if (count++ == 0){
                    throw new ThreeException();
                }
                System.out.println("no Exception");
            }catch (ThreeException e){
                System.out.println("ThreeException");
            }finally {
                System.out.println("in finally cause");
                if(count == 2)
                    break;
            }
        }
    }
}

class ThreeException extends Exception{}
Copy the code

Let’s look at the output:

ThreeException
in finally cause
no Exception
in finally cause
Copy the code

If we have a return statement inside a try or catch block, will the finally statement be executed? Let’s look at the following example:

public class MultipleReturns {
    public static void f(int i){
        System.out.println("start.......");
        try {
            System.out.println("1");
            if(i == 1)
                return;
            System.out.println("2");
            if (i == 2)
                return;
            System.out.println("3");
            if(i == 3)
                return;
            System.out.println("else");
            return;
        }finally {
            System.out.println("end");
        }
    }

    public static void main(String[] args) {
        for(int i = 1; i<4; i++){ f(i); }}}Copy the code

Let’s look at the results:

start.......
1
end
start.......
1
2
end
start.......
1
2
3
end
Copy the code

We see that the finally clause executes even if we use a return statement ina try or catch block. So when does the finally clause not execute? There are two situations in which Java exceptions can be lost

  • Overwriting an exception in finally (overwriting another exception in finally overwrites the previously caught exception)
  • Return in finally clause (that is, return)

Java exception stack

In this section, we use a simple example to get a more intuitive understanding of the content of the exception stack. When we look at Exception, the Exception method is at the top, the main method is at the bottom, and there are other call levels in between. This is actually the stack structure, first in, last out. Let’s take a look at the following examples:

public class WhoCalled {
    static void f() {
        try {
            throw new Exception();
        } catch (Exception e) {
            for (StackTraceElement ste : e.getStackTrace()){
                System.out.println(ste.getMethodName());
            }
        }
    }

    static void g(){
        f();
    }

    static void h(){
        g();
    }

    public static void main(String[] args) {
        f();
        System.out.println("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --");
        g();
        System.out.println("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --");
        h();
        System.out.println("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --"); }}Copy the code

Let’s look at the output:

f
main
---------------------------
f
g
main
---------------------------
f
g
h
main
---------------------------
Copy the code

You can see that the exception message is coming from the inside out. My understanding of the exception is to look at the first exception message from the inside out, because that is the source of the exception.

Rethrows the exception and the exception chain

We know that every time we encounter an exception, we need to do a try… Catch, one is fine, what if there are multiple exceptions? Categorizing can be tricky, so use an Exception to handle all exceptions. This is indeed possible, but it will inevitably lead to increased maintenance difficulties later. The best way to do this is to encapsulate the exception information and then capture our wrapper class. Throws throws throws for superior processing; throws throws throws for superior processing; throws throws throws for superior processing; throws throws throws for superior processing. Catch (v.) But what does this have to do with this? The try… We don’t need to do any processing for the catch block of catch, just use the keyword throw to actively throw our encapsulated exception information. Throws continue with the method exception after passing the keyword throws. Its upper layers can do the same, and so on to produce a chain of exceptions. By using exception chains, we can improve code comprehension, system maintainability, and friendliness. After we catch an exception, we typically do two things

  • Throw the original exception after you catch it, and you want to keep the latest exception throw point –fillStackTrace
  • Once caught, a new exception is thrown, hoping to throw a full chain of exceptions –initCause

Catch the exception and throw it again

An Exception is caught in a function, and no further processing is done in the catch module. Instead, a catch(Exception e){throw e; }, let’s use an example:

public class ReThrow {
    public static void f()throws Exception{
        throw new Exception("Exception: f()");
    }

    public static void g() throws Exception{
        try{
            f();
        }catch(Exception e){
            System.out.println("inside g()");
            throw e;
        }
    }
    public static void main(String[] args){
        try{
            g();
        }
        catch(Exception e){
            System.out.println("inside main()"); e.printStackTrace(System.out); }}}Copy the code

Let’s look at the output:

inside g() inside main() java.lang.Exception: Exception: (f) / / exception thrown an exception or initial function f () at the com. Learn. Example. ReThrow. F (RunMain. Java: 5) the at com.learn.example.ReThrow.g(RunMain.java:10) at com.learn.example.RunMain.main(RunMain.java:21)Copy the code

FillStackTrace — Overrides the preceding exception throw point (gets the latest exception throw point)

Set catch(Exception e){(Exception) e.illinstackTrace (); } let’s see by example :(same example)

public void g() throws Exception{
    try{
        f();
    }catch(Exception e){
    	System.out.println("inside g()"); throw (Exception)e.fillInStackTrace(); }}Copy the code

The running results are as follows:

inside g() inside main() java.lang.Exception: Exception: F () / / show is the latest point at com. Learn. Example. ReThrow. G (RunMain. Java: 13) at com. Learn. Example. RunMain. Main (RunMain. Java: 21)Copy the code

Catch an exception and throw a new exception (retain the original exception information, as opposed to catch an exception and throw it again)

If we need to preserve the original exception information when we throw an exception, there are two ways to do so

  • E = new Exception(); e.initCause(ex);
  • E =new Exception(ex);
class ReThrow {
    public void f(){ try{ g(); }catch(NullPointerException ex){// 表 1 Exception e=new Exception(); // save the original exception message e.initCause(ex); E =new Exception(ex); try { throw e; } catch (Exception e1) { e1.printStackTrace(); } } } public void g() throws NullPointerException{ System.out.println("inside g()");
        throw new NullPointerException();
    }
}

public class RunMain {
    public static void main(String[] agrs) {
    	try{
            new ReThrow().f();
        }
        catch(Exception e){
            System.out.println("inside main()"); e.printStackTrace(System.out); }}}Copy the code

In this example, we catch a NullPointerException and then throw an Exception. If we do not use initCause to save the original NullPointerException, NullPointerException will be lost. Only Eception exceptions are displayed. Here are the results:

/ / not call initCause method output inside g () Java. Lang. Exception at com. Learn. Example. ReThrow. F (RunMain. Java: 9) at Com. Learn. Example. RunMain. Main (31) RunMain. Java: / / call initCasue method to save the output of the original Exception information inside g () Java. Lang. Exception at com.learn.example.ReThrow.f(RunMain.java:9) at com.learn.example.RunMain.main(RunMain.java:31) Caused by: java.lang.NullPointerException at com.learn.example.ReThrow.g(RunMain.java:24) at com.learn.example.ReThrow.f(RunMain.java:6) ... 1 moreCopy the code

We see that when we save using the initCause method, the original exception message is output as Caused by.

Restrictions on Java exceptions

There are limitations when Java exceptions encounter inheritance or interfaces, and let’s take a look at some of them.

  • Rule 1: When a subclass overrides a method that its parent throws, it either does not throw an exception, or it throws an exception that is identical to the parent method or a subclass of that exception. Methods overridden by subclasses can throw unchecked exceptions if the overridden parent method only throws checked exceptions. For example, if a parent method throws a checked IOException, the method cannot be overridden to throw an Exception. For checked exceptions, only IOException and its subclasses can be thrown, as well as unchecked exceptions. Let’s take a look at some examples:
class A {  
    public void fun() throws Exception {}  
}  
class B extends A {  
    public void fun() throws IOException, RuntimeException {}  
}
Copy the code

The exception thrown by the parent class contains all exceptions, which is written correctly.

class A {  
    public void fun() throws RuntimeException {}  
}  
class B extends A {  
    public void fun() throws IOException, RuntimeException {}  
}
Copy the code

IOException is outside of its parent class.

class A {  
    public void fun() throws IOException {}  
}  
class B extends A {  
    public void fun() throws IOException, RuntimeException, ArithmeticException{}
}
Copy the code

RuntimeException is outside the scope of IO and is outside the scope of its parent class. But RuntimeException and ArithmeticException are runtime exceptions, and methods overridden by subclasses can throw any RuntimeException. So this is correct.

  • Rule: When a subclass overrides a method that its parent throws, if it implements an interface with the same method signature and the method in that interface also has an exception declaration, the method overridden by the subclass either does not throw an exception, or it throws the intersection of the overridden method declaration exception in the parent class and the implemented method declaration exception in the interface.
class Test {
    public Test() throws IOException {}
    void test() throws IOException {}
}

interface I1{
    void test() throw Exception;
}

class SubTest extends Test implements I1 {
    public SubTest() throws Exception,NullPointerException, NoSuchMethodException {}
    void test() throws IOException {}
}
Copy the code

In the SubTest class, the test method either does not throw an exception, or it throws IOException or a subclass of it (for example, InterruptedIOException).

Java exceptions and constructors

If an exception occurs in a constructor, how can we handle it correctly? You might say use finally. Doesn’t it always execute? This may not be true. If the constructor encounters an exception during its execution and some part of the object is not properly initialized, then cleaning it up in finally can be problematic. Rule: For classes that might throw an exception during the constructor phase and require cleanup, the safest way is to use nested try clauses.

try {
    InputFile in=new InpputFile("Cleanup.java");
    try {
    	String string;
    	int i=1;
    	while((string=in.getLine())! =null) {} }catch (Exception e) { System.out.println("Cause Exception in main");
    	e.printStackTrace(System.out);
    }finally {
    	in.dispose();
    }
}catch (Exception e) {
    System.out.println("InputFile construction failed");
}
Copy the code

If the constructor fails and throws an exception, it will be caught by the outermost catch. In this case, the Dispose method of InputFile object does not need to be executed. If constructed successfully, the second try block is entered, at which point the finally block must be called (the object needs to be disposed).

Usage Guide for exceptions (Use exceptions in the following situations)

  • Handle exceptions at the right level (catch exceptions only if you know how to handle them)
  • Try to resolve the problem and re-invoke the method that generated the exception
  • Make a few fixes, then re-execute around the exception
  • Do as much as you can in the current runtime environment, and then toss the same exception higher up
  • Do as much as you can in your current running environment, and then toss the different exceptions higher up
  • Work to make libraries and programs safer