Java exceptions are a feature of the Java language. It’s something that you use a lot in everyday coding. But do you really know anything about anomalies?

Here are some classic interview questions about exceptions:

  • What are the Java class structures and major inheritance relationships related to exceptions?
  • What syntax improvements have Java7 made regarding exceptions?
  • What are runtime and declarative exceptions? What’s the difference?
  • What is the “exception loss (exception override)” problem?
  • What is an exception chain?
  • What is return value override?
  • What are some best practices when writing exceptions?

If you know the answers to all of these questions, you’re already familiar with Java exceptions.

If some questions are not clear? It doesn’t matter. Just finish reading this article.

The hierarchy of exceptions

The above first

Leaving aside the exceptions below, we are likely to focus on four categories:

  • Throwable
  • Error
  • Exception
  • RuntimeException

Because Error stands for “Error”, most of them are serious errors. OutOfMemoryError and StackOverflowError are two classes you should be familiar with if you know about JVMS.

The Exception and RuntimeException classes are probably the ones we use most when we write code.

Is it better to inherit from Exception or RuntimeException? We’ll cover this later in the “Best Practices for writing exceptions” section.

Java 7 and abnormal

Java7 makes two improvements to exceptions. The first is try-with-resources, and the second is catch multiple exceptions.

try-with-resources

Try-with-resources is grammatical sugar. This is essentially an automatic call to the resource’s close() function. This is similar to the with statement in Python.

Instead of try-with-resources, we usually write something like this when using a resource object such as IO:

String getReadLine(a) throws IOException {
    BufferedReader br = new BufferedReader(fileReader);
    try {
        return br.readLine();
    } finally {
        if(br ! =null) br.close(); }}Copy the code

Use try-with-recources:

String getReadLine(a) throws IOException {
    try (BufferedReader br = new BufferedReader(fileReader)) {
        returnbr.readLine(); }}Copy the code

Obviously, the compiler automatically adds bytecode after try-with-resources to determine whether the object is null and, if not, to call close().

Only objects that implement the java.lang.AutoCloseable interface, or java.io.Closable (which actually follows from the java.lang.AutoCloseable) interface, will have their close() function called automatically.

A bit different is that java.io.Closable requires an implementor to ensure that the close function can be called repeatedly. AutoCloseable close() should not be idempotent. For details, see Javadoc.

However, it is important to note that try-with-resources can be overridden, meaning that exceptions thrown by the catch block can be overridden by exceptions thrown when close() is called. We’ll talk about exception override in the next section.

Multiple exception capture

Directly on the code:

public static void main(String[] args) {
    try {
        int a = Integer.parseInt(args[0]);
        int b = Integer.parseInt(args[1]);
        int c = a / b;
        System.out.println("The result is:" + c);
    } catch (IndexOutOfBoundsException | NumberFormatException | ArithmeticException ie) {
        System.out.println("One of these three anomalies occurred.");
        ie.getMessage();
        // When catching multiple exceptions, the exception variable is final by default,
        // The following code has an error:
        // ie = new ArithmeticException("test");}}Copy the code

Suppressed

What if both the catch and finally blocks throw exceptions? See the analysis below.

Runtime exceptions and declarative exceptions

Runtime exceptions are runtimeexceptions, and you don’t have to explicitly catch a RuntimeException or declare it on a method.

On the other hand, if your Exception is just an Exception, it needs to be caught explicitly.

Sample code:

void test(a) {
    hasRuntimeException();
    try {
        hasException();
    } catch(Exception e) { e.printStackTrace(); }}void hasException(a) throws Exception {
    throw new Exception("exception");
}

void hasRuntimeException(a) {
    throw new RuntimeException("runtime");
}
Copy the code

Although we can see from the Exception structure diagram, RuntimeException inherits from Exception. Java, however, treats runtime exceptions “specially.” So if you need this kind of exception in your program, you can inherit RuntimeException.

And if you’re not explicitly asking for exceptions to be caught and handled by higher levels, we recommend using run-time exceptions in favor of them because they make your code simpler.

What is exception coverage

As we mentioned earlier, it is possible to throw an exception when a finally block calls a resource’s close() method. At the same time we might throw another exception in the catch block. Then the exception thrown by the catch block will be “eaten” by the exception of the finally block.

Look at this code. What does a call to the test() method output?

void test(a) {
    try {
        overrideException();
    } catch(Exception e) { System.out.println(e.getMessage()); }}void overrideException(a) throws Exception {
    try {
        throw new Exception("A");
    } catch (Exception e) {
        throw new Exception("B");
    } finally {
        throw new Exception("C"); }}Copy the code

It prints C. As you can see, B in the catch block is eaten.

The JDK provides two methods of Suppressed to resolve this issue:

// Calling test prints:
// C
// A
void test(a) {
    try {
        overrideException();
    } catch(Exception e) { System.out.println(e.getMessage()); Arrays.stream(e.getSuppressed()) .map(Throwable::getMessage) .forEach(System.out::println); }}void overrideException(a) throws Exception {
    Exception catchException = null;
    try {
        throw new Exception("A");
    } catch (Exception e) {
        catchException = e;
    } finally {
        Exception exception = new Exception("C");
        exception.addSuppressed(catchException);
        throwexception; }}Copy the code

Abnormal chain

When you throw a new exception, you can use the initCause method to indicate which exception is causing the exception, resulting in a chain of exceptions.

Please refer to the article about exception chain before the public account for details.

Return value override

Similar to the previous exception override problem, a finally block overwrites the return values of a try and catch block.

So best practice is not to use return in finaly blocks!!

Best practices?

  1. Use RuntimeException whenever possible
  2. Try not tofinallyBlock throws an exception or returns a value
  3. Try to use Java7’s new syntax for exceptions
  4. Try-catch blocks can be pulled into a separate method to make your code cleaner — see Chapter 7 of Code Cleanliness
  5. Logging exceptions can be combined with log andprintStackTrace

Refer to the article

  • Try-with-resources analysis in Java7
  • Java 7 provides multiple exception catching
  • New exception handling feature for Java7 -addSuppressed() method, etc
  • The Code Clean Way