Talk about Java exceptions

Keywords: Java Back-end Programmer

In the process of developing applications, the ideal state is not to have errors. Even if errors occur, they should be detected during compilation. However, in reality, errors will occur from time to time when the program runs, which will not be expected. If ignored, it will lead to program crash.

In C language, the program will indicate the error by returning an abnormal value, and the programmer will detect and handle the possible exceptions by using operations such as if else, which will make the code very cumbersome and bloated. Java provides an error capture mechanism for exception handling, handles exceptions in programs, reduces error checking, and provides reliable applications.

Suppose an error occurs while a Java program is running. This error may be caused by the error information contained in the file, or there is a problem with the network connection. It may also be caused by using an invalid array subscript, or trying to use an object reference that is not assigned a value.

Abnormal system

Java provides a series of objects that describe the wrong type based on the idea of object-oriented, with type information itself. It is an exception system represented by Throwable as the base class of exceptions, and java.lang.Throwable as the base class can be thrown instead of all exception classes, but it is not recommended.

The Java language provides three Throwables: run-time exception, checked exception, and error.

  • Error: used to describe the internal error and resource exhaustion error of the Java runtime system. The program should not throw this type of object.
  • Run time exception: run-time exception, possible errors of RuntimeException class and its subclasses during operation. The compiler does not check for such exceptions. This kind of exception belongs to non searchable exception, which is generally caused by program logic error. You can choose to capture and handle it or not in the program.
  • checked exception: checked exception. In Exception, except RuntimeException and its subclasses, the compiler will check this kind of Exception. If this kind of Exception occurs in the program, you must declare or catch the Exception, otherwise the compilation cannot pass.

checked exception and run-time exception are both branches of the Exception class hierarchy.

The principle for using exceptions is that if the caller is expected to recover appropriately, the checked exception should be used for this situation.

However, each checked exception declared to be thrown is a condition that forces the programmer to handle the exception, so as to enhance reliability. But this way will increase the burden on programmers. When the programmer cannot handle the checked exception correctly, the unchecked exception may be more appropriate.

Therefore, if the caller cannot recover the error, an unchecked exception should be thrown. If you can recover and want to force the caller to handle the exception, return an Optional value. In case of failure and insufficient information, the inspected exception should be thrown.

Note: the difference between Exception and Error: Exception can be handled by the program itself, but Error cannot be handled.

Declaration exception

If you don't want to handle exceptions in a method, you can use the throws keyword to declare the exceptions to be thrown in the method. Examples are as follows:

public int read(File file) throws IOException {
    ...
}

When reading the file content, no matter whether the file does not exist or the content is empty, IOException exception will be thrown out.

Not all cases need to use throws to declare the exception to be thrown. Remember that exceptions should be thrown in the following four cases:

  1. Call a method that throws a checked exception, such as the FileInputStream constructor.
  2. An error is found during the running of the program, and a detected exception is thrown by using the throw statement.
  3. An error occurs in the program. For example, a[-1]=0 will throw a non checked exception such as ArrayIndexOutOfBoundsException.
  4. Internal error in Java virtual machine and runtime library.

For Java methods that may be used by others, the exceptions that may be thrown by this method should be declared at the beginning of the method according to the exception specification.

When declaring exceptions, do not declare Error and its subclass errors in Java, nor do you need to declare which non checked exceptions inherited from RuntimeException.

public static void get(int index) throws ArrayIndexOutOfBoundsException { ยทยทยท }

These runtime exceptions are completely under control.

In a word, a method must declare all possible checked exceptions thrown, and the non checked exceptions are either uncontrollable (Error), and the occurrence of RuntimeException should be avoided if the gate is to be opened. If the method does not declare a possible checked exception, the compiler issues an Error message.

Throw exception

The above only introduces the declaration of exceptions, but how do exceptions occur? An example is given below:

public File getFile(String path) throws FileNotFoundException {
    File file = new File(path);
    if (file.exists()) {
        return file;
    }
    throw new FileNotFoundException(path + " File Not Found!");
}

From the above example, we can see that the declaration exception is generated because an exception is thrown using the throw keyword, indicating that the file is not found. The expression of throw new FileNotFoundException() thrown above can also be changed to the following expression:

FileNotFoundException ex = new FileNotFoundException();
throw e;

Once a method throws an exception, it is impossible for the method to return to the caller. That is, you don't have to worry about the returned default value or error code.

Custom exception

When there is no standard Exception class in the Java Exception system that can fully describe the problem, you can customize an Exception class to describe it. Java custom exceptions only need to be derived from the Exception class and its subclasses. As follows:

public class ErrorDetail {
    public ErrorDetail(ErrorCode errorCode, String errorMsg) {
        this.errorCode = errorCode;
        this.errorMsg = errorMsg;
    }
    private final ErrorCode errorCode;
    private final String errorMsg;
    @Override
    public String toString() {
        return "code:" + errorCode + ", msg:" + errorMsg;
    }
}
public class CustomException extends Exception {
    private ErrorDetail detail;
    public CustomException(ErrorDetail detail) { this.detail = detail; }
    @Override
    public String toString() { return "CustomException [" + detail + "]"; }
}

You can define your own variables and methods in a custom exception class to pass additional exception information. Although you can define your own exception classes, the exception classes provided in the standard library are preferred. There are many benefits to using standard exceptions:

  1. Make the API easier to learn and use.
  2. Make the API more readable.
  3. The fewer exception classes, the smaller the memory footprint, and the less time it takes to load these classes.

When the thrown Error is equivalent to the semantics of the Exception class in the standard library, you can use the standard Exception. However, do not reuse Exception, RuntimeException, Throwable or Error directly. Because these Exception classes are the base classes of other Exception classes and are equivalent to abstract classes, these exceptions cannot be reliably tested.

Catch exception

The process of declaring an exception and throwing an exception is very simple. As long as you throw it, you don't pay attention to it, because the method declaring the exception has handed over the control of the exception to the calling method. Of course, sometimes you don't need to declare exceptions, but handle them.

Catch exception

In Java, you need to use the try catch statement block to catch exceptions. The following is how to execute the try catch statement block:

  1. When the try statement block does not throw an exception, execute step 3 after executing the try statement block; When an exception is thrown in the try statement block, skip the try statement and execute step 2 for the rest of the code; The try statement block throws an exception type that is not declared in the catch clause, and the method where the try catch statement block is located will exit immediately.
  2. Execute the processor code in the catch clause.
  3. Try catch statement block execution completed.

An example of reading data is given below:

public void read(File file) {
    try {
        InputStream in = new FileInputStream(file);
        int len;
        byte[] buf = new byte[4096];
        while ((len = in.read(buf)) != -1) {
            System.out.println(new String(buf, 0, len));
        }
    } catch (IOException ex) {
        // Process IOException
    } 
}

As shown above, when the in.read() method throws an IOException, it will jump out of the while loop, enter the catch clause, and generate a stack trace.

Catch multiple exceptions

When multiple exceptions are thrown in the try statement block, you need to use the catch clause to deal with different types of exceptions. Catch clauses are executed sequentially, and only one is executed. An example is given below:

public Object readObject(InputStream in) {
    try {
        ObjectInputStream ois = new ObjectInputStream(in);
        return ois.readObject();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

When there is no subclass relationship between the exception types in the above multiple catch clauses, you can use the same catch clause introduced by Java SE 7 to capture multiple exception types. As follows:

public Object readObject(InputStream in) {
    try {
        ObjectInputStream ois = new ObjectInputStream(in);
        return ois.readObject();
    } catch (IOException | ClassNotFoundException e) {
        e.printStackTrace();
    }
}

You can also replace multiple exceptions handled by the catch clause above with the base class Exception of the Exception type. As follows:

public Object readObject(InputStream in) {
    try {
        ObjectInputStream ois = new ObjectInputStream(in);
        return ois.readObject();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

When an exception is caught, do not ignore it through an empty catch clause. As follows:

try {
    ...
} catch (CustomException e) {
    // Ignore
}

After ignoring exceptions, the program will quietly execute in case of errors, rather than prompt errors. It may bury hidden dangers in future procedures. It is best to handle exceptions correctly, avoid failures, or throw exceptions, resulting in failure of programs, thereby retaining useful information for debugging later.

Abnormal translation

If the exception thrown has no obvious connection with the task to be performed, it will be confusing and "pollute" the higher-level API with implementation details. Therefore, the high-level implementation should catch the low-level exceptions and throw the exceptions implemented according to the high-level abstraction. This practice is called exception translation. The following example uses the catch clause to wrap the caught exception as the required exception and throw it again:

try {
    ObjectInputStream ois = new ObjectInputStream(in);
    return ois.readObject();
} catch (IOException | ClassNotFoundException e) {
    throw new SerializationException(e);
} 

Abnormal chain

Exception chain is a special kind of exception translation. If low-level exceptions are helpful to high-level exception debugging, you can use exception chain. All subclasses that implement Throwable can use the getCause() method to get the original exception and construct a new exception as a parameter.

public class CustomException extends Exception {
    public CustomException(Throwable cause) {
        super(cause);
    }
}

Most standard exceptions have constructors that support chains. For exceptions without a support chain, you can use the Throwable initCause(Throwable cause) method to set cause. The exception chain not only allows you to access the cause through the program, but also integrates the stack trace of the cause into higher-level exceptions.

Exception translation is better than throwing exceptions directly, but it can not be abused. It is best to ensure that low-level methods can execute successfully and avoid throwing exceptions. If the exception from the lower layer cannot be blocked or handled, exception translation is generally used; Secondly, if the exception thrown by the low-level method conforms to the higher-level, the exception is propagated to the higher-level, and the appropriate high-level exception can be thrown using exception translation.

Stack trace

When the program fails because the exception is not caught, the system will automatically print the stack trace of the exception. The stack trace contains the description of the exception, that is, the toString() method of the exception. It usually contains the class name of the exception, followed by a detail message.

You can use the printStackTrace() method provided by the Throwable class to access the information of the stack trace.

Throwable t = new Throwable();
StringWriter out = new StringWriter();
t.printStackTrace(new PrintWriter(out));
String desc = out.toString();

You can also get a StackTraceElement array object describing the stack track through the getStackTrace() method. As follows:

public class PrintStackTrace {
    static void first() throws CustomException {
        second();
    }
    static void second() throws CustomException {
        third();
    }
    static void third() throws CustomException {
        throw new CustomException(new ErrorDetail(ErrorDetail.ErrorCode.PASSWORDERROR, "Password error"));
    }
    public static void main(String[] args) {
        try {
            first();
        } catch (CustomException e) {
            for (StackTraceElement stackTraceElement : e.getStackTrace()) {
                System.out.println(stackTraceElement);
            }
        }
    }
}

The output results are as follows:

xxx.xxx.xxx.PrintStackTrace.third(PrintStackTrace.java:23)
xxx.xxx.xxx.PrintStackTrace.second(PrintStackTrace.java:18)
xxx.xxx.xxx.PrintStackTrace.first(PrintStackTrace.java:13)
xxx.xxx.xxx.PrintStackTrace.main(PrintStackTrace.java:30)

Because stack information is the information that must be checked when investigating the cause of a program failure. If the failure situation is not easy to reproduce, it will be very difficult or even impossible to obtain more information. Therefore, return as much information as possible about the cause of the failure in the toString() method for analysis. But never include passwords, keys, and similar information in stack messages!

finally clause

When a try catch statement block is executed, the code throws an exception, executes the exception handling in the corresponding catch clause, and exits the execution of this method. However, we hope that some code will be called after processing the try catch statement block. Java provides a finally clause to solve this problem. In the try catch finally statement block, the code in the finally clause will be executed whether there is an exception or not.

The execution order of the try catch finally statement block is as follows:

  1. Execute the try statement block normally. When the code in the try statement block is executed, execute step 3; If try throws an exception, proceed to step 2.
  2. Execute the catch clause matching the exception thrown in step 1. After exception handling, execute step 3.
  3. Code that executes the finally clause.

An example of try catch finally is given below:

public void read(File file) {
    InputStream in = null;
    try {
        in = new FileInputStream(file);
        int len;
        byte[] buf = new byte[4096];
        while ((len = in.read(buf)) != -1) {
            System.out.println(new String(buf, 0, len));
        }
    } catch (IOException ex) {
        // Exception Handler
    } finally {
        try {
            Objects.requireNonNull(in).close();
        } catch (IOException e) {
            // Exception Handler
        }
    }
}

In the above code, whether the catch clause is called to handle exceptions or not, the finally clause will be called to close the InputStream resource. There are also special cases where the finally clause will not be executed:

  1. An exception occurred in the finally clause.
  2. In the previous code, use System.exit(int status) to exit the program.
  3. The thread on which the program is located dies.
  4. Turn off the cpu.

Therefore, when using the try catch finally statement block, put the code block that may throw exception types into the try statement block for execution; Put the code that can always be executed no matter how the try statement block exits in the finally clause.

In fact, it is a good choice to use the try finally statement to ensure that resources will be closed in time, but it is recommended to decouple the above example from the try catch and try finally statement blocks to improve the clarity of the code.

public void read(File file) {
    InputStream in = null;
    try {
        try {
            in = new FileInputStream(file);
            int len;
            byte[] buf = new byte[4096];
            while ((len = in.read(buf)) != -1) {
                System.out.println(new String(buf, 0, len));
            }
        } finally {
            in.close();
        }
    } catch (IOException ex) {
        ex.printStackTrace();
    } 
}

When there is a return statement in both the try clause and the finally clause, the code segment in the finally clause will be executed, and the return value of the finally clause will overwrite the return value in the try clause. As follows:

public static int process(int value) {
    try {
        return value * value;
    } finally {
        if (value == 2) {
            return 0;
        }
    }
}

Note: try, catch and finally clauses cannot be used alone. They can form three statement blocks: try catch, try finally and try catch finally.

try-with-resources

The above example uses the try finally statement block to close resources. Put the resources to be closed in the finally clause to ensure that the resources are closed. However, this syntax will be too cumbersome, but this situation has been improved in Java SE 7.

Java SE 7 introduces the try with resources syntax to close resources. Using this syntax, you need to implement the java.lang.autoclosable interface:

public interface AutoCloseable {
    void close() throws Exception;
}

The Closeable in Java 5 has been modified, and the modified interface inherits the autoclosable interface. All objects that implement the Closeable interface support the try with resources feature.

In addition, the Closeable interface in Java SE 5 also inherits the autoclosable interface. The difference from autoclosable is that the exception types thrown are different:

public interface Closeable extends AutoCloseable {
    public void close() throws IOException;
}

If you write a class that represents resources that must be closed, you also need to implement the autoclosable interface.

The following is to use the try with resources syntax to simplify the above code:

public void read(File file) {
    InputStream in = null;
    try (InputStream in = new FileInputStream()) {
        in = new FileInputStream(file);
        int len;
        byte[] buf = new byte[4096];
        while ((len = in.read(buf)) != -1) {
            System.out.println(new String(buf, 0, len));
        }
    } catch (IOException ex) {
        // Exception Handler
    } 
}

Using try with resources makes the code more concise and understandable. Therefore, try with resources is preferred over try finally.

summary

Exceptions are an integral part of Java. Therefore, to learn how to use exceptions correctly, a good exception handling mechanism will ensure the robustness of the program and improve the availability of the system. However, too many and incorrect exception handling will also drag down the running efficiency of the program. Therefore, use exceptions only when errors are inevitable, and try to avoid handling exceptions.

More content, please pay attention to the official account of the sea.

Posted by egorig on Sun, 21 Nov 2021 22:13:23 -0800