Java programming note 10: exceptions

Java programming note 10: exceptions

Source: PHP Chinese network

Exception classes in Java inherit from Throwable, which is the base class of all exceptions. There are several important exception types:

[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-pl58x5we-1643025395881) (C: \ users \ 70748 \ appdata \ roaming \ typora user images \ image-20220123183114503. PNG)]

Throwable is the base class. All subtypes of throwable can be thrown or caught as exceptions. Exception is an exception type that developers often deal with. If you need to throw an exception in the program, you can use the built-in subclass inherited from exception or create an exception class inherited from exception. Therefore, exception and its subclass exceptions can be called "customer exception". Exceptions such as Error are managed by the JVM and generally do not need developers to worry about, so they can be called "system exceptions". RuntimeException is a subtype of exception. This kind of exception is special. It represents an exception that cannot be detected at compile time and occurs only when the system is running, so it is called "runtime exception".

Basics

Take a look at the simplest exception example:

package ch10.create;

class MyExcetion extends Exception {
}

public class Main {
    private static void testException() throws MyExcetion {
        throw new MyExcetion();
    }

    public static void main(String[] args) {
        try {
            testException();
        } catch (Exception e) {
            e.printStackTrace();
            // ch10.create.MyExcetion
            // at ch10.create.Main.testException(Main.java:8)
            // at ch10.create.Main.main(Main.java:13)
        }
    }
}

This example contains the most basic components of Java exceptions:

  • Exception definition
  • Throw exception
  • Exception declaration
  • Exception capture

These parts are described below.

Exception definition

To create a user-defined Exception, it is easy to create a class and integrate Exception, just like MyException shown in the above example.

Generally speaking, the most important information of an exception is the exception type itself, so its purpose can be distinguished and used by the name of the built-in exception:

package ch10.create2;

public class Main {
    private static void testException(Object o) {
        if (o == null) {
            throw new NullPointerException();
        }
        System.out.println(o.toString());
    }

    public static void main(String[] args) {
        testException("Hello World!");
        testException(null);
        // Hello World!
        // Exception in thread "main" java.lang.NullPointerException
        // at ch10.create2.Main.testException(Main.java:9)
        // at ch10.create2.Main.main(Main.java:16)
    }
}

In this example, testException checks whether the parameter o is null before using it. If so, it throws a built-in exception NullPointerException, indicating that the passed in parameter is null rather than a valid object, and the program cannot continue to run.

For another example, when a computer performs mathematical operations, there may be a "division by zero problem", which can also be expressed by exceptions:

package ch10.create3;

public class Main {
    private static double divide(double a, double b) {
        if (b == 0) {
            throw new ArithmeticException();
        }
        return a / b;
    }

    public static void main(String[] args) {
        System.out.println(divide(1, 5));
        System.out.println(divide(10, 0));
        // 0.2
        // Exception in thread "main" java.lang.ArithmeticException
        //         at ch10.create3.Main.divide(Main.java:13)
        //         at ch10.create3.Main.main(Main.java:21)
    }
}

The built-in exception ArithmeticException here represents the exception (including division by zero) when performing mathematical operations.

Do we need to check and throw exceptions like this when using objects or performing division operations? Of course not. This is because these common exceptions inherit from RuntimeException and belong to the runtime exception mentioned earlier. During runtime, if the JVM finds that the object member is to be used, but the object is null, or the divisor is 0 when performing division operation, the corresponding exception will be generated automatically and the exception information will be printed:

package ch10.create4;

public class Main {
    private static double divide(int a, int b) {
        return a / b;
    }

    public static void main(String[] args) {
        System.out.println(divide(1, 5));
        System.out.println(divide(10, 0));
        // 0.0
        // Exception in thread "main" java.lang.ArithmeticException: / by zero
        //         at ch10.create4.Main.divide(Main.java:5)
        //         at ch10.create4.Main.main(Main.java:10)
    }
}

If you look closely, you may find that I changed the type of divide parameter to int instead of the original double. This is because if it is not modified, no exception will be generated here, but a strange Infinity will be output.

This is because in the computer world, shaping is an exact value, but floating-point numbers are approximate and have precision limitations. Therefore, 0.0 is not necessarily equal to 0, and we can't directly compare two floating-point numbers with = =. Therefore, there is a problem in the previous code, which should be written as follows:

	...
	private static double divide(double a, double b) {
        if ((b - 0) < 0.0001) {
            throw new ArithmeticException();
        }
        return a / b;
    }
	...

This also explains why 10.0/0.0=Infinity. In fact, the floating-point number 0.0 here is an infinite number close to 0 but not 0, such as 0.00000000000001. If you divide it by 10.0, the natural result is infinity.

In addition to the exception type itself, you can also add some additional information to the exception:

package ch10.create6;

class MyExcetion extends Exception {
    public MyExcetion(String msg) {
        super(msg);
    }
}

public class Main {
    private static void testException() throws MyExcetion {
        throw new MyExcetion("this is a message.");
    }

    public static void main(String[] args) {
        try {
            testException();
        } catch (Exception e) {
            e.printStackTrace();
            // ch10.create6.MyExcetion: this is a message.
            // at ch10.create6.Main.testException(Main.java:11)
            // at ch10.create6.Main.main(Main.java:16)
        }
    }
}

This is a construction method using the exception base class Throwable:

    public Throwable(String message) {
        fillInStackTrace();
        detailMessage = message;
    }

This method can save a string as information to the exception.

Throw exception

Throwing an exception is very simple. Just use the throw keyword (interestingly, Python uses raise), but it should be noted that in Java, depending on the type of exception thrown, the method also needs to be changed:

package ch10.throw1;

class MyException extends Exception {

}

public class Main {
    private static void throwCheckedException() throws MyException {
        throw new MyException();
    }

    private static void throwUnCheckedException() {
        throw new RuntimeException();
    }

    public static void main(String[] args) {
        try {
            throwCheckedException();
        } catch (MyException e) {
            e.printStackTrace();
        }
        throwUnCheckedException();
        // ch10.throw1.MyException
        //         at ch10.throw1.Main.throwCheckedException(Main.java:10)
        //         at ch10.throw1.Main.main(Main.java:19)
        // Exception in thread "main" java.lang.RuntimeException
        //         at ch10.throw1.Main.throwUnCheckedException(Main.java:14)
        //         at ch10.throw1.Main.main(Main.java:23)
    }
}

As shown above, MyException is a custom Exception inherited from Exception. To throw this Exception directly in the method, you need to add an Exception declaration in the method, which refers to the throws keyword and its subsequent parts. However, it is not necessary to throw a built-in Exception RuntimeException.

Thinking in Java translates Exception Declare into "exception description".

This is because exceptions can be divided into two categories by "need to add exception declaration when throwing":

  • Checked exceptions refer to exceptions inherited from exceptions, but excluding runtimeexceptions.
  • Unchecked Exception refers to the exception inherited from Error or RuntimeException.

If the method will throw the checked exception when calling the method, or create and throw such an exception directly in the current method, you must use try Catch catches and handles exceptions, or adds an exception declaration to the method declaration to directly throw the exception upward.

The design of Java exceptions is quite unique. There are no similar "checked exceptions" in Python and PHP. Most programming languages that support exceptions are quite simple. If an exception occurs, there are only two results:

  1. Do not deal with the exception capture, directly let the exception throw upward until the compiler / interpreter of the language catches it, terminate the program and display the relevant information of the call stack.
  2. Capture and handle exceptions.

The reason why Java is designed in this way may be due to two considerations:

  • Some exceptions must be handled, such as those generated after opening a resource that needs to be closed.
  • Actively handling exceptions can improve the robustness of programs.

But in reality, using exceptions to improve the robustness of programs is just an "idealized idea". In fact, in most cases, we can't deal with exceptions effectively. The only thing we can do is throw them up. We can't do more than report errors.

Finally, it should be added that the exception declaration can declare multiple exceptions at the same time:

package ch10.throw2;

class MyException extends Exception {
}

class MyException2 extends Exception {
}

public class Main {
    private static void throwCheckedException(int i) throws MyException, MyException2 {
        if (i > 10) {
            throw new MyException();
        } else {
            throw new MyException2();
        }
    }

    public static void main(String[] args) {
        try {
            throwCheckedException(10);
        } catch (MyException e) {
            e.printStackTrace();
        } catch (MyException2 e) {
            e.printStackTrace();
        }
    }
}

You can even add an exception declaration without actually "throwing an exception":

...
public class Main {
    private static void throwCheckedException(int i) throws MyException, MyException2 {
        ;// do nothing.
    }
	...
}

Using this feature, we can "bury" possible future exceptions in the abstract base class in advance.

Catch exception

Use try Catch statement can catch exceptions and handle them as necessary:

package ch10.catch1;

class MyException extends Exception {
}

public class Main {
    public static void main(String[] args) {
        try {
            throw new MyException();
        } catch (MyException e) {
            ;
        }
    }
}

The part in the try is called the "monitored region". If this part of the code will produce exceptions, it will be caught by the subsequent catch part and execute the corresponding processing statements.

Of course, the above example will not produce any output, because there are no statements in catch. In this case, the program is called "swallow exception" just like "no exception". This situation may cover up the existing errors of the program and should be avoided.

printStackTrace

Of course, you can also print the exception object directly, but a more common method is to use the printStackTrace method to print the call stack of the exception. You can quickly locate the problem by observing the relevant information of the call stack:

package ch10.catch2;

class MyException extends Exception {
}

public class Main {
    private static void g() throws MyException {
        throw new MyException();
    }

    private static void f() throws MyException {
        g();
    }

    public static void main(String[] args) {
        try {
            f();
        } catch (MyException e) {
            e.printStackTrace();
            // ch10.catch2.MyException
            // at ch10.catch2.Main.g(Main.java:8)
            // at ch10.catch2.Main.f(Main.java:12)
            // at ch10.catch2.Main.main(Main.java:17)
        }
    }
}

The default printStackTrace method will print the call stack information to the standard error stream. Of course, by default, both the standard error stream and the standard output stream point to the console.

printStackTrace has an overloaded method that can receive printStream parameters:

    ...
	public void printStackTrace(PrintStream s) {
        printStackTrace(new WrappedPrintStream(s));
    }
	...

Therefore, the default printStackTrace can be regarded as printStackTrace(System.err). Similarly, we can use this method to redirect error information to other output streams, such as standard output (System.out):

    ...
	public static void main(String[] args) {
        try {
            f();
        } catch (MyException e) {
            e.printStackTrace(System.out);
        }
    }
	...

Logger

In addition, it can also be combined with the built-in log class java util. logging. Logger to output exception information to the log:

	...
	public static void main(String[] args) {
        try {
            f();
        } catch (MyException e) {
            StringWriter sw = new StringWriter();
            e.printStackTrace(new PrintWriter(sw));
            Logger logger = Logger.getLogger("xyz.icexmoon.java-notes.ch10.ctch4.Main");
            logger.severe(sw.toString());
        }
    }
	...

You can use logger The getlogger method obtains a log instance, and its parameter is the name of the log instance. Generally, it is customary to use the complete class name with package name for naming.

The class of Logger used to process and record logs is logging Handler, this is an abstract class. By default, a subclass ConsoleHandler will be used, that is, output to the console. If you want the log to be output to a file, you can use FileHandler:

package ch10.catch4;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.logging.FileHandler;
import java.util.logging.Logger;

class MyException extends Exception {
}

public class Main {
	...
    public static void main(String[] args) {
        try {
            f();
        } catch (MyException e) {
            StringWriter sw = new StringWriter();
            e.printStackTrace(new PrintWriter(sw));
            Logger logger = Logger.getLogger("xyz.icexmoon.java-notes.ch10.ctch4.Main");
            FileHandler fh;
            try {
                String fname = "D:\\workspace\\java\\java-notebook\\xyz\\icexmoon\\java_notes\\ch10\\catch4\\exp.log";
                fh = new FileHandler(fname);
                logger.addHandler(fh);
                logger.severe(sw.toString());
            } catch (SecurityException e1) {
                e1.printStackTrace();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }
}

Viewing the log file represented by fname, you can find that the log information of xml structure will be written:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
  <date>2022-01-24T08:33:52.892243300Z</date>
  <millis>1643013232892</millis>
  <nanos>243300</nanos>
  <sequence>0</sequence>
  <logger>xyz.icexmoon.java-notes.ch10.ctch4.Main</logger>
  <level>SEVERE</level>
  <class>ch10.catch4.Main</class>
  <method>main</method>
  <thread>1</thread>
  <message>ch10.catch4.MyException
	at ch10.catch4.Main.g(Main.java:14)
	at ch10.catch4.Main.f(Main.java:18)
	at ch10.catch4.Main.main(Main.java:23)
</message>
</record>
</log>

Catch all exceptions

Multiple catch statements can be set to catch exceptions. The matching rule is to match in order. Once the matching is successful, the corresponding exception handling statements will be executed, and the remaining catch statements will be skipped:

package ch10.all;

import java.util.Random;

class MyException1 extends Exception {
}

class MyException2 extends Exception {
}

class MyException3 extends Exception {
}

public class Main {
    private static void throwExp(int i) throws MyException1, MyException2, MyException3 {
        if (i < 3) {
            throw new MyException1();
        } else if (i >= 6) {
            throw new MyException2();
        } else {
            throw new MyException3();
        }
    }

    public static void main(String[] args) {
        Random random = new Random();
        try {
            throwExp(random.nextInt(10));
        } catch (MyException1 e1) {
            System.out.println("e1:" + e1);
        } catch (MyException2 e2) {
            System.out.println("e2:" + e2);
        } catch (MyException3 e3) {
            System.out.println("e3:" + e3);
        }
    }
}

Exception matching actually includes inheritance relationships. For example, all exceptions we need to catch can be caught through exception, so the above example can also be rewritten as:

    ...
    public static void main(String[] args) {
        Random random = new Random();
        try {
            throwExp(random.nextInt(10));
        } catch (Exception e) {
            System.out.println("e:" + e);
        }
    }
    ...

We can take Exception as the last Exception matching detection, and put more detailed Exception types ahead:

	...
	public static void main(String[] args) {
        Random random = new Random();
        try {
            throwExp(random.nextInt(10));
        } catch (MyException1 e1) {
            System.out.println("check exp1: " + e1);
        } catch (MyException2 e2) {
            System.out.println("check exp2: " + e2);
        } catch (MyException3 e3) {
            System.out.println("check exp3: " + e3);
        } catch (Exception e) {
            System.out.println("default exp check: " + e);
        }
    }
    ...

finally

Sometimes, no matter whether an exception occurs or not, we want to execute some specific statements. The most common is when we open the resources (files, networks, etc.) that need to be released.

The following is an example of opening a file and reading it line by line:

package ch10.finally1;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class Main {
    public static void main(String[] args) {
        String fn = "D:\\workspace\\java\\java-notebook\\xyz\\icexmoon\\java_notes\\ch10\\catch4\\exp.log";
        BufferedReader br = null;
        try {
            br = new BufferedReader(new FileReader(fn));
            String line = null;
            do {
                line = br.readLine();
                System.out.println(line);
            } while (line != null);
            try {
                br.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (Exception e) {
            try {
                br.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }
}

In fact, it's better to use finally. You should use try after the resource is successfully created catch... Finally, use resources and ensure that resources can always be released:

package ch10.finally2;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class Main {
    public static void main(String[] args) {
        String fn = "D:\\workspace\\java\\java-notebook\\xyz\\icexmoon\\java_notes\\ch10\\catch4\\exp.log";
        BufferedReader br = null;
        try {
            br = new BufferedReader(new FileReader(fn));
            try {
                String line = null;
                do {
                    line = br.readLine();
                    System.out.println(line);
                } while (line != null);

            } catch (IOException e) {
                throw e;
            } finally {
                try {
                    br.close();
                } catch (IOException e1) {
                    throw e1;
                }
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Using finally can make the code more concise.

The statements in finally will be executed in any case, including the case of break or continue:

package ch10.finally3;

public class Main {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            try {
                System.out.println("now i=" + i);
                if (i == 2) {
                    break;
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                System.out.println("finally block is executed.");
            }
        }
        // now i=0
        // finally block is executed.
        // now i=1
        // finally block is executed.
        // now i=2
        // finally block is executed.
    }
}

Even using the return statement in the try block cannot prevent finally from executing:

package ch10.finally4;

import java.util.Random;

public class Main {
    private static void testFinally(int i) {
        try {
            if (i < 5) {
                System.out.println("i<5");
                return;
            } else {
                System.out.println("i>=5");
                return;
            }
        } finally {
            System.out.println("finally block is executed.");
        }
    }

    public static void main(String[] args) {
        Random random = new Random();
        testFinally(random.nextInt(10));
        // i>=5
        // finally block is executed.
    }
}

Even if the exception is not normally caught by the current code, but continues to be thrown upward, finally will also be executed:

package ch10.finally5;

class MyExp extends Exception {
}

public class Main {
    public static void main(String[] args) throws MyExp {
        try {
            throw new MyExp();
        } finally {
            System.out.println("finally block is executed.");
        }
    }
}
// finally block is executed.
// Exception in thread "main" ch10.finally5.MyExp
//         at ch10.finally5.Main.main(Main.java:9)

Stack track

As mentioned earlier, the abnormal call stack can be printed through printStackTrace. Actually calling the stack is simply the call track of the function, which can be seen as pressing the functions into the stack one by one according to the call order.

You can get the call stack directly through the exception and view the information of the elements in it:

package ch10.stack_trace;

import util.Fmt;

class MyExp extends Exception {
}

public class Main {
    private static void f() throws MyExp {
        throw new MyExp();
    }

    private static void g() throws MyExp {
        f();
    }

    public static void main(String[] args) {
        try {
            g();
        } catch (MyExp e) {
            for (StackTraceElement ste : e.getStackTrace()) {
                Fmt.printf("line:%d method:%s\n", ste.getLineNumber(), ste.getMethodName());
            }
            // line:10 method:f
            // line:14 method:g
            // line:19 method:main
        }
    }
}

The stack order of functions is main(), go(), f(), so the stack order is opposite, which is f(), g(), main().

Re throw exception

Sometimes we will throw the exception again after catching the exception. At this time, the call stack information of the exception will not change. This can be found by printing with printStackTrace:

package ch10.stack_trace2;

import util.Fmt;

class MyExp extends Exception {
}

public class Main {
    private static void f() throws MyExp {
        throw new MyExp();
    }

    private static void g() throws MyExp {
        try {
            f();
        } catch (MyExp e) {
            throw e;
        }
    }

    public static void main(String[] args) {
        try {
            g();
        } catch (MyExp e) {
            e.printStackTrace();
            // ch10.stack_trace2.MyExp
            // at ch10.stack_trace2.Main.f(Main.java:10)
            // at ch10.stack_trace2.Main.g(Main.java:15)
            // at ch10.stack_trace2.Main.main(Main.java:23)
        }
    }
}

If you want to rewrite the contents of the call stack when throwing the exception again, you can use the fillInStackTrace method:

...
public class Main {
	...
    private static void g() throws MyExp {
        try {
            f();
        } catch (MyExp e) {
            e.fillInStackTrace();
            throw e;
        }
    }

    public static void main(String[] args) {
        try {
            g();
        } catch (MyExp e) {
            e.printStackTrace();
            // ch10.stack_trace3.MyExp
            // at ch10.stack_trace3.Main.g(Main.java:15)
            // at ch10.stack_trace3.Main.main(Main.java:22)
        }
    }
}

The call stack is re recorded from where the fillInStackTrace method was called.

Abnormal chain

In addition to re throwing the original exception, you can also create a new exception and throw it, but the problem is that doing so will lose the relevant information of the original exception, which is unfavorable to troubleshooting.

This problem can be solved by establishing an "Exception chain". The so-called Exception chain is to associate the original Exception with the new Exception. Java is realized by adding constructors to several basic Exception classes: Exception, Error and RuntimeException:

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

The parameter cause here is the original exception that raises the new exception.

However, except for these basic exceptions, most other exceptions do not implement the relevant constructor, but provide an initCause method to set the association to the original exception:

	...
	public synchronized Throwable initCause(Throwable cause) {
		...
        return this;
    }
    ...

Here is a simple example:

package ch10.stack_trace4;

class MyExp extends Exception {
}

public class Main {
    private static void f() throws MyExp {
        throw new MyExp();
    }

    private static void g() throws MyExp {
        try {
            f();
        } catch (MyExp e) {
            MyExp newExp = new MyExp();
            newExp.initCause(e);
            throw newExp;
        }
    }

    public static void main(String[] args) {
        try {
            g();
        } catch (MyExp e) {
            e.printStackTrace();
            // ch10.stack_trace4.MyExp
            //         at ch10.stack_trace4.Main.g(Main.java:15)
            //         at ch10.stack_trace4.Main.main(Main.java:23)
            // Caused by: ch10.stack_trace4.MyExp
            //         at ch10.stack_trace4.Main.f(Main.java:8)
            //         at ch10.stack_trace4.Main.g(Main.java:13)
            //         ... 1 more
        }
    }
}

Of course, you can throw a new exception by throwing new runtimeException (E). The effect is similar.

Abnormal loss

In some cases, abnormal loss occurs and is difficult to detect, such as:

package ch10.dispose;

class MyExp extends Exception {
}

class MyExp2 extends Exception {
}

public class Main {
    private static void throwMyExp2() throws MyExp2 {
        throw new MyExp2();
    }

    public static void main(String[] args) throws Exception {
        try {
            throw new MyExp();
        } finally {
            throwMyExp2();
        }
    }
}
// Exception in thread "main" ch10.dispose.MyExp2
//         at ch10.dispose.Main.throwMyExp2(Main.java:11)
//         at ch10.dispose.Main.main(Main.java:18)

An exception is thrown in the try, but there is no corresponding capture and processing statement, and finally calls a function that will also produce an exception. In this way, the final exception thrown upward is the MyExp2 exception caused by finally, not the original MyExp exception.

A simpler example of missing exception:

package ch10.dispose2;

class MyExp extends Exception {
}

public class Main {
    private static void throwMyExp() {
        try {
            throw new MyExp();
        } finally {
            return;
        }
    }

    public static void main(String[] args) {
        throwMyExp();
    }
}

Through the finally statement, directly using return to end the function call can make the originally thrown exception disappear, as if nothing had happened. Fortunately, in this case, the IDE will prompt that the finally block is not closed normally.

Limitation of exceptions

In Java, everything becomes complicated when it comes to inheritance, and so do exceptions. Generally speaking, if inheritance and method coverage are involved, the Richter substitution principle still needs to be observed, that is, the child class instance must be able to be used as the parent class instance.

Such restrictions are reflected in two aspects:

  1. Methods overridden by subclasses can only "reduce" the exception declaration of parent methods, not increase them.
  2. The exception type declared in the method overridden by the subclass can only be the subclass of the exception in the parent method, not the superclass.

Of course, the exceptions mentioned here belong to "checked exceptions". After all, "unchecked exceptions" do not involve exception declaration.

The first limitation is easy to understand. After all, if a subclass can add new exception types at will when overriding methods, it means that the original code dealing with the parent class needs to be modified. This obviously violates the Richter substitution principle and is detrimental to polymorphism.

For example:

package ch10.limit;

class MyExp extends Exception {
}

class MyExp2 extends Exception {
}

class Parent {
    public void test() throws MyExp {
    }
}

class Child extends Parent {
    @Override
    public void test() throws MyExp,MyExp2 {
        super.test();
    }
}

public class Main {
    private static void dealParent(Parent parent) {
        try {
            parent.test();
        } catch (MyExp e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        Parent parent = new Parent();
        dealParent(parent);
        Child child = new Child();
        dealParent(child);
    }
}

dealParent function can call the test method of Parent and its subclasses polymorphically, but because Child added a new exception declaration when rewriting the test method, dealParent function cannot run normally. Of course, in fact, because of Java's restrictions on exceptions, the above code cannot be compiled.

The second limitation can be analogized Java Programming Notes 5: polymorphism - konjac black tea's blog (icexmoon.xyz) The concept of covariant return types mentioned in is consistent. If the subclass overrides the method with the exception of the subtype, the exception of the subtype can be treated as the exception of the action parent type, so it will not violate the Richter substitution principle.

package ch10.limit2;

class MyExp extends Exception {
}

class MyExp2 extends MyExp {
}

class Parent {
    public void test() throws MyExp {
    }
}

class Child extends Parent {
    @Override
    public void test() throws MyExp2 {
    }
}
...

This is allowed. Perhaps it can be called "covariant exception statement" in private?

Like the return value, the exception declaration is also not included in the "parameter signature", that is, it will not be used as the identification of parameter overloading.

In addition, unlike ordinary methods, constructors have no similar limitations:

package ch10.limit3;

class MyExp extends Exception {
}

class MyExp2 extends Exception {
}

class Parent {
    public Parent() throws MyExp {
    }
}

class Child extends Parent {

    public Child() throws MyExp, MyExp2 {
        super();
    }
}

public class Main {

    public static void main(String[] args) throws MyExp, MyExp2 {
        Parent parent = new Parent();
        Child child = new Child();
    }
}

It seems that the above method violates the Richter substitution principle, but in fact, the Richter substitution principle is aimed at polymorphism, and the creation of objects and constructors does not involve polymorphism, so there will be no relevant restrictions.

constructor

If the constructor involves exceptions, it will also be troublesome. Here, a program that can read files line by line is used as an example:

package ch10.constructor;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

class FileInput {
    BufferedReader reader;

    public FileInput(String fname) throws FileNotFoundException {
        FileReader fr;
        try {
            fr = new FileReader(fname);
            reader = new BufferedReader(fr);
        } catch (FileNotFoundException e) {
            throw e;
        }
    }

    public String readLine() throws IOException {
        return reader.readLine();
    }

    public void close() throws IOException {
        reader.close();
    }

}

public class Main {
    public static void main(String[] args) {
        String fname = "D:\\workspace\\java\\java-notebook\\xyz\\icexmoon\\java_notes\\ch10\\catch4\\exp.log";
        try {
            FileInput fi = new FileInput(fname);
            try {
                String line = null;
                do {
                    line = fi.readLine();
                    System.out.println(line);
                } while (line != null);
            } finally {
                fi.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Because the FileInput object may throw an exception when it is created, the new FileInput must be wrapped with try. At the same time, because FileInput contains a file resource, it is necessary to call the close method to close the resource after it is created. Therefore, it is necessary to use try after the FileInput object is created Finally statement to ensure fi Close can be called at any time.

Try is not used here catch... Finally, but catch more basic IOException type exceptions in the outer catch, which can simplify the code.

Even though I have used some methods to simplify this sample code as much as possible, it can be seen that if the constructor involves exceptions, the client code will inevitably become more complex and may contain multiple levels of try Catch structure.

Simplified exception

Although Java's exception mechanism looks good, it actually adds a lot of trouble to developers, especially "checked exceptions".

Developers often need to spend more time adding try Catch and exception description, if inheritance is involved, it will undoubtedly be more troublesome.

In fact, we can simplify exception handling to some extent without losing exceptions to improve coding efficiency.

Output exception to console

As the entry function of the Java program, the peers can add exception declaration. At this time, the corresponding exception capture code can be omitted and the corresponding exception can be directly passed to the JVM through main. The JVM will directly call the printStackTrace of the exception and output the exception information to the console:

package ch10.simple;

class MyExp extends Exception {
}

public class Main {
    public static void main(String[] args) throws MyExp {
        throw new MyExp();
    }
}
// Exception in thread "main" ch10.simple.MyExp
//         at ch10.simple.Main.main(Main.java:8)

Convert to unchecked exception

If the checked exception is converted into an unchecked exception, the relevant problems will not be involved:

package ch10.simple2;

class MyExp extends Exception {
}

public class Main {
    public static RuntimeException exchangeExp(Exception e) {
        return new RuntimeException(e);
    }

    public static void main(String[] args) {
        try {
            throw new MyExp();
        } catch (MyExp e) {
            throw exchangeExp(e);
        }
    }
}
// Exception in thread "main" java.lang.RuntimeException: ch10.simple2.MyExp
//         at ch10.simple2.Main.exchangeExp(Main.java:8)
//         at ch10.simple2.Main.main(Main.java:15)
// Caused by: ch10.simple2.MyExp
//         at ch10.simple2.Main.main(Main.java:13)

reference material:

Keywords: Java Back-end Exception

Added by PHP Novice on Tue, 25 Jan 2022 08:55:30 +0200