Detailed explanation and exception handling in Java

brief introduction

An unexpected event that occurs when a program is running, which prevents the program from executing normally as expected by the programmer. This is an exception. When an exception occurs, do you allow the program to live and die, exit and terminate immediately, or output an error to the user? Or C language style: using function return value as execution state?.

Java provides a better solution: exception handling mechanism.

The exception handling mechanism enables the program to handle the exception according to the preset exception handling logic of the code when the exception occurs, so that the program can return to normal and continue to execute as much as possible, and keep the code clear.

The exception in Java can be caused by the execution of the statement in the function or thrown manually by the programmer through the throw statement. As long as an exception is generated in the Java program, an exception object of the corresponding type will be used to encapsulate the exception, and JRE will try to find an exception handler to handle the exception.

Throwable class is the top-level parent class of Java exception type. Only when an object is an instance of throwable class (directly or indirectly), it is an exception object and can be recognized by the exception handling mechanism. Some common exception classes are built in JDK, and we can also customize exceptions.

Classification and class structure diagram of Java exceptions

Some general exceptions are built in the Java standard pants. These classes take Throwable as the top-level parent class.

Throwable also derives Error class and Exception class.

Error: the instance of the error class and its subclasses represents the error of the JVM itself. Errors cannot be handled by programmers through code, and errors rarely appear. Therefore, programmers should pay attention to various Exception classes under the branch with Exception as the parent class.

Exception: exception and its subclasses represent various unexpected events sent by the program runtime. It can be used by Java exception handling mechanism and is the core of exception handling.

In general, we divide the exception classes into two categories according to the requirements of Javac for exception handling.

Unchecked exception: Error and RuntimeException and their subclasses. java c will not prompt and find such exceptions during compilation, and it is not required to handle these exceptions in the program. So if you like, we can write code to handle such exceptions (using try...catch...finally) or not. For these exceptions, we should modify the code instead of handling them through the exception handler. Most of the reasons for such exceptions are code writing problems. For example, in addition to 0 error ArithmeticException, wrong forced type conversion error ClassCastException, array index out of bounds ArrayIndexOutOfBoundsException, null object NullPointerException, etc.

checked exception: other exceptions except Error and RuntimeException. javac forces programmers to prepare for such exceptions (using try...catch...finally or throws). In the method, either use the try catch statement to capture it and process it, or use the throw clause to declare and throw it, otherwise the compilation will not pass. Such exceptions are generally caused by the running environment of the program. Because the program may be run in various unknown environments, and the programmer cannot interfere with how the user uses the program written by him, the programmer should be prepared for such exceptions. Such as sqlexception, IOException, classnotfoundexception, etc.

It should be clear that check and non check are for javac, which is easy to understand and distinguish.

Initial anomaly

The following code demonstrates two exception types: ArithmeticException and inputmismatch exception. The former is caused by dividing the integer by 0, and the latter is caused by the input data that cannot be converted to int type.

package com.example;
import java. util .Scanner ;
public class AllDemo
{
      public static void main (String [] args )
      {
            System . out. println( "----Welcome to the command line division calculator----" ) ;
            CMDCalculate ();
      }
      public static void CMDCalculate ()
      {
            Scanner scan = new Scanner ( System. in );
            int num1 = scan .nextInt () ;
            int num2 = scan .nextInt () ;
            int result = devide (num1 , num2 ) ;
            System . out. println( "result:" + result) ;
            scan .close () ;
      }
      public static int devide (int num1, int num2 ){
            return num1 / num2 ;
      }
}
/*****************************************

----Welcome to the command line division calculator----
2
0
Exception in thread "main" java.lang.ArithmeticException : / by zero
     at com.example.AllDemo.devide( AllDemo.java:30 )
     at com.example.AllDemo.CMDCalculate( AllDemo.java:22 )
     at com.example.AllDemo.main( AllDemo.java:12 )

----Welcome to the command line division calculator----
1
r
Exception in thread "main" java.util.InputMismatchException
     at java.util.Scanner.throwFor( Scanner.java:864 )
     at java.util.Scanner.next( Scanner.java:1485 )
     at java.util.Scanner.nextInt( Scanner.java:2117 )
     at java.util.Scanner.nextInt( Scanner.java:2076 )
     at com.example.AllDemo.CMDCalculate( AllDemo.java:20 )
     at com.example.AllDemo.main( AllDemo.java:12 )
*****************************************/

I only heard the voice of the architect from the architect's office: Recalling Gaoyang, people scattered. Who will match the first couplet or the second couplet?

Exceptions are triggered when a function is executed, and the function is called hierarchically, forming a call stack, because as long as an exception occurs to a function, all its caller s will be affected by the exception. When these affected functions are output with exception information, an exception tracking stack is formed.

The place where the exception first occurs is called the exception throw point.

As can be seen from the above example, when the divide by 0 exception occurs in the devide function, the devide function will throw an ArithmeticException exception, so the call to his CMDCalculate function cannot be completed normally, so the exception is also sent, and the caller main of cmdccalculate also has an exception because the exception is thrown by cmdccalculate, so it always goes back to the bottom of the call stack. This behavior is called exception bubbling. Exception bubbling is to find the nearest exception handler in the current exception function or the caller of this function. Since no exception handling mechanism is used in this example, the exception is finally thrown to JRE by the main function, resulting in program termination.

The above code can be compiled without exception handling mechanism, because both exceptions are non check exceptions. However, the following example must use the exception handling mechanism, because the exception is to check the exception.

In the code, I choose to use throws to declare exceptions and let the caller of the function deal with possible exceptions. But why only throw IOException? Because FileNotFoundException is a subclass of IOException, it is within the processing range.

This code is by Java Architect must see network-Structure Sorting
@Test
public void testException() throws IOException
{
    //The constructor of FileInputStream will throw FileNotFoundException
    FileInputStream fileIn = new FileInputStream("E:\\a.txt");
    
    int word;
    //The read method will throw IOException
    while((word =  fileIn.read())!=-1) 
    {
        System.out.print((char)word);
    }
    //The close method throws an IOException
    fileIn.clos
}

Basic syntax of exception handling

When writing code to handle exceptions, For checking exceptions, there are two different ways to handle them: using try catch... The finally statement block handles it. Alternatively, use the throws declaration in the function signature and give it to the function caller caller to solve it.

try...catch...finally statement block

try{
     //Put the code that may cause exceptions in the try block.
     //If the try is completed and no exception occurs, then execute the finally block and the code after finally (if any).
     //If an exception occurs, try to match the catch block.

}catch(SQLException SQLexception){
    //Each catch block is used to catch and handle a specific exception, or a subclass of this exception type. In Java 7, multiple exceptions can be declared in one catch.
    //The parentheses after catch define the exception type and exception parameters. If the exception matches it and is the first to match, the virtual machine will use this catch block to handle the exception.
    //In the catch block, you can use the exception parameters of this block to obtain the relevant information of the exception. Exception parameters are local variables in this catch block and cannot be accessed by other blocks.
    //If the exception in the current try block is not caught in all subsequent catch es, execute finally first, and then match the exception handler in the external caller of this function.
    //If no exception occurs in the try, all catch blocks will be ignored.

}catch(Exception exception){
    //...
}finally{
   
    //finally blocks are usually optional.
   //Whether the exception occurs or not, and whether the exception matches and is processed, finally will execute.
   //A try must have at least one catch block, otherwise, at least one finally block. But finally is not used to handle exceptions. Finally will not catch exceptions.
  //finally, it mainly does some cleaning work, such as closing the stream, closing the database connection, etc. 
}

Points needing attention

1. Local variables in try block, catch block (including exception variables) and finally cannot be shared.

2. Each catch block is used to handle an exception. Exception matching is searched from top to bottom according to the order of catch blocks. Only the first matched catch will be executed. When matching, it not only runs exact matching, but also supports parent matching. Therefore, If multiple catch exception types under the same try block have parent-child relationship, you should put the child exception in front and the parent exception in the back, so as to ensure that each catch block has the meaning of existence.

3. In java, the task of exception handling is to transfer the execution control flow from the place where the exception occurs to the place where the exception can be handled . In other words, when an exception occurs to a statement of a function, the statement behind the statement will not be executed, and it loses focus. The execution flow jumps to the nearest matching exception handling catch code block for execution. After the exception is handled, the execution flow will then execute after "the catch code block that handled this exception".

In some programming languages, when the exception is handled, the control flow will return to the exception throwing point and then execute. This strategy is called: resumption model of exception handling

Java restores the execution flow to the catch block that handles the exception and then executes it. This strategy is called: termination model of exception handling

This code is by Java Architect must see network-Structure Sorting
public static void main(String[] args){
        try {
            foo();
        }catch(ArithmeticException ae) {
            System.out.println("Handling exceptions");
        }
}
public static void foo(){
        int a = 5/0;  //Exception throw point
        System.out.println("Why don't you give me a raise!!!");  //////////////////////Will not execute
}

throws function declaration

Throws declaration: if the code inside a method will throw checked exception s, and the method itself does not completely handle them, javac ensures that you must use the throws keyword on the method signature to declare these possible exceptions, otherwise the compilation will not pass.

Throws is another way to handle exceptions, which is different from try catch... Finally, throws only declares the possible exceptions in the function to the caller, but does not deal with them in detail.

The reason for this exception handling may be that the method itself does not know how to handle such exceptions, or let the caller handle them better, and the caller needs to be responsible for possible exceptions.

public void foo() throws ExceptionType1 , ExceptionType2 ,ExceptionTypeN
{ 
     //foo can throw exceptions of exceptiontype1, exceptiontype2, exceptiontypen, or exception objects of their subclasses.
}

finally block

Finally block no matter whether an exception occurs or not, as long as the corresponding try is executed, it must also be executed. There is only one way to make the finally block not execute: system exit(). Therefore, finally blocks are usually used to release resources: close files, close database connections, and so on.

A good programming habit is to open resources in the try block and clean up and release these resources in the finally block.

Points needing attention:

1. finally blocks do not have the ability to handle exceptions. Only catch blocks can handle exceptions.

2. In the same try catch... In the finally block, if an exception is thrown in the try and there is a matching catch block, execute the catch block first and then the finally block. If there is no catch block matching, execute finally first, and then go to the external caller to find the appropriate catch block.

3. In the same try catch... In the finally block, if an exception occurs in the try and an exception is thrown when the exception is handled in the matched catch block, the following finally will also be executed: first execute the finally block, and then find the appropriate catch block from the peripheral callers.

This is normal, but there are exceptions. finally, there are many disgusting, eccentric, strange and difficult problems. At the end of this article, I will uniformly introduce elevator speed - >: finally block and return

Throw exception throw statement

throw exceptionObject

Programmers can also manually and explicitly throw an exception through the throw statement. Throw statement must be followed by an exception object.

The throw statement must be written in the function. The place where the throw statement is executed is an exception throw point, which is no different from the exception throw point automatically formed by JRE.

public void save(User user)
{
      if(user  == null) 
          throw new IllegalArgumentException("User Object is empty");
      //......
        
}

Abnormal chaining

In some large-scale and modular software development, once an exception occurs in one place, it will lead to A series of exceptions like domino effect. Suppose that module B needs to call the method of module A to complete its own logic. If module A has an exception, module B will not be able to complete and an exception will occur. However, when B throws an exception, it will cover up the exception information of module A, which will lose the root information of the exception. The chaining of exceptions can connect the exceptions of multiple modules in series, so that the exception information will not be lost.

Exception Chaining: construct a new exception object with an exception object as a parameter. The new alien object will contain information about the previous exception. This technology is mainly implemented by a function with Throwable parameter of exception class. This exception as a parameter is called cause.

Looking at the source code of Throwable class, we can find that there is a Throwable field cause, which saves the root exception parameters passed during construction. This design is the same as the node class design of the linked list, so it is natural to form a chain.

public class Throwable implements Serializable {
    private Throwable cause = this;
   
    public Throwable(String message, Throwable cause) {
        fillInStackTrace();
        detailMessage = message;
        this.cause = cause;
    }
     public Throwable(Throwable cause) {
        fillInStackTrace();
        detailMessage = (cause==null ? null : cause.toString());
        this.cause = cause;
    }
    
    //........
} 

The following is an example to demonstrate the chaining of exceptions: input two ints from the command line, add them, and output. If the input number is not int, it will cause getInputNumbers exception, which will cause the add function exception. You can throw it in the add function

A chained exception.

public static void main(String[] args)
{
    
    System.out.println("Please enter 2 addends");
    int result;
    try
    {
        result = add();
        System.out.println("result:"+result);
    } catch (Exception e){
        e.printStackTrace();
    }
}
//Get the input 2 integers and return
private static List<Integer> getInputNumbers()
{
    List<Integer> nums = new ArrayList<>();
    Scanner scan = new Scanner(System.in);
    try {
        int num1 = scan.nextInt();
        int num2 = scan.nextInt();
        nums.add(new Integer(num1));
        nums.add(new Integer(num2));
    }catch(InputMismatchException immExp){
        throw immExp;
    }finally {
        scan.close();
    }
    return nums;
}

//Perform addition calculation
private static int add() throws Exception
{
    int result;
    try {
        List<Integer> nums =getInputNumbers();
        result = nums.get(0)  + nums.get(1);
    }catch(InputMismatchException immExp){
        throw new Exception("Calculation failed",immExp);  /////////////////////////////Chaining: construct a new exception object with an exception object as a parameter.
    }
    return  result;
}

/*
Please enter 2 addends
r 1
java.lang.Exception: Calculation failed
    at practise.ExceptionTest.add(ExceptionTest.java:53)
    at practise.ExceptionTest.main(ExceptionTest.java:18)
Caused by: java.util.InputMismatchException
    at java.util.Scanner.throwFor(Scanner.java:864)
    at java.util.Scanner.next(Scanner.java:1485)
    at java.util.Scanner.nextInt(Scanner.java:2117)
    at java.util.Scanner.nextInt(Scanner.java:2076)
    at practise.ExceptionTest.getInputNumbers(ExceptionTest.java:30)
    at practise.ExceptionTest.add(ExceptionTest.java:48)
    ... 1 more

*/

Custom exception

If you want to customize the Exception class, you can extend the Exception class. Therefore, such custom exceptions belong to checked exception s. If you want to customize non check exceptions, extend from RuntimeException.

According to international conventions, custom exceptions should always contain the following constructors:

  • A parameterless constructor
  • A constructor with a String parameter and passed to the constructor of the parent class.
  • A constructor with String parameter and Throwable parameter, which are passed to the parent class constructor
  • A constructor with Throwable parameter and passed to the constructor of the parent class.

The following is the complete source code of IOException class, which can be used for reference.

public class IOException extends Exception
{
    static final long serialVersionUID = 7818375828146090155L;

    public IOException()
    {
        super();
    }

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

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

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

Abnormal precautions

1. When a subclass overrides a function with a throws declaration of the parent class, the exception declared by its throws must be within the controllable range of the parent class exception - the exception handler used to handle the throws method of the parent class must also be applicable to the throws method of the subclass. This is to support polymorphism.

For example, if the parent class method throws two exceptions, the child class cannot throw three or more exceptions. If the parent class throws IOException, the subclass must throw IOException or subclass of IOException.

As for why? I think maybe the following example can illustrate.

class Father
{
    public void start() throws IOException
    {
        throw new IOException();
    }
}

class Son extends Father
{
    public void start() throws Exception
    {
        throw new SQLException();
    }
}
/**********************Assume that the above code is allowed (essentially wrong)***********************/
class Test
{
    public static void main(String[] args)
    {
        Father[] objs = new Father[2];
        objs[0] = new Father();
        objs[1] = new Son();

        for(Father obj:objs)
        {
        //Because the essence thrown by the Son class is SQLException, and IOException cannot handle it.
        //So try here.. catch cannot handle exceptions in Son.
        //Polymorphism cannot be achieved.
            try {
                 obj.start();
            }catch(IOException)
            {
                 //Handling IOException
            }
         }
   }
}

View Code

2. Java programs can be multithreaded. Each thread is an independent execution flow and an independent function call stack. If the program has only one thread, exceptions that are not handled by any code will cause the program to terminate. If it is multithreaded, an exception that is not handled by any code will only cause the thread where the exception is located to end.

In other words, exceptions in Java are thread independent. The thread problem should be solved by the thread itself, not entrusted to the outside, and will not directly affect the execution of other threads.

finally block and return

First, a fact that is not easy to understand: in the try block, even if there are return, break, continue and other statements that change the execution flow, finally will execute.

public static void main(String[] args) { int re = bar(); System.out.println(re); } private static int bar() { try{ return 5; } finally{ System.out.println("finally"); } } /*Output: finally 5 */

When facing this problem, many people are always summarizing the order and law of execution, but I think it is still difficult to understand. I summed up a method myself. Use the following GIF diagram to illustrate.

That is to say: try catch... All returns in finally are executed as long as they can be executed. They jointly write the return value to the same memory address (assuming the address is 0x80). The later executed data will overwrite the data executed first, and the return value taken by the real caller is the last written value. Then, according to this idea, the following example is not difficult to understand.

Return in finally overrides the return value in try or catch.

public static void main(String[] args)
    {
        int result;
        
        result  =  foo();
        System.out.println(result);     /////////2
        
        result = bar();
        System.out.println(result);    /////////2
    }

    @SuppressWarnings("finally")
    public static int foo()
    {
        trz{
            int a = 5 / 0;
        } catch (Exception e){
            return 1;
        } finally{
            return 2;
        }

    }

    @SuppressWarnings("finally")
    public static int bar()
    {
        try {
            return 1;
        }finally {
            return 2;
        }
    }

return in finally suppresses (eliminates) the exceptions in the previous try or catch block

class TestException
{
    public static void main(String[] args)
    {
        int result;
        try{
            result = foo();
            System.out.println(result);           //Output 100
        } catch (Exception e){
            System.out.println(e.getMessage());    //No exceptions were caught
        }
        
        
        try{
            result  = bar();
            System.out.println(result);           //Output 100
        } catch (Exception e){
            System.out.println(e.getMessage());    //No exceptions were caught
        }
    }
    
    //Exceptions in catch are suppressed
    @SuppressWarnings("finally")
    public static int foo() throws Exception
    {
        try {
            int a = 5/0;
            return 1;
        }catch(ArithmeticException amExp) {
            throw new Exception("I will be ignored because of the following finally Used in return");
        }finally {
            return 100;
        }
    }
    
    //Exceptions in try are suppressed
    @SuppressWarnings("finally")
    public static int bar() throws Exception
    {
        try {
            int a = 5/0;
            return 1;
        }finally {
            return 100;
        }
    }
}

The exception in finally will overwrite (eliminate) the exception in the previous try or catch

class TestException
{
    public static void main(String[] args)
    {
        int result;
        try{
            result = foo();
        } catch (Exception e){
            System.out.println(e.getMessage());    //Output: I am the Exception in final
        }
        
        
        try{
            result  = bar();
        } catch (Exception e){
            System.out.println(e.getMessage());    //Output: I am the Exception in final
        }
    }
    
    //Exceptions in catch are suppressed
    @SuppressWarnings("finally")
    public static int foo() throws Exception
    {
        try {
            int a = 5/0;
            return 1;
        }catch(ArithmeticException amExp) {
            throw new Exception("I will be ignored because of the following finally A new exception was thrown in");
        }finally {
            throw new Exception("I am finaly Medium Exception");
        }
    }
    
    //Exceptions in try are suppressed
    @SuppressWarnings("finally")
    public static int bar() throws Exception
    {
        try {
            int a = 5/0;
            return 1;
        }finally {
            throw new Exception("I am finaly Medium Exception");
        }
        
    }
}

The above three examples are different from ordinary people's coding thinking, so I suggest:

  • Don't use return in fianlly.
  • Don't throw an exception in finally.
  • Lighten the task of finally. Don't do anything else in finally. Finally blocks are most appropriate only to release resources.
  • Try to write all return s at the end of the function instead of try catch ... Finally.

Added by trystan on Sun, 27 Feb 2022 08:46:55 +0200