[Java 学习笔记] 异常处理

目录

异常的类别

异常的捕获与抛出

打印出方法的调用栈 

示例1:

示例2:

注意

自定义异常


总结自廖雪峰老师的Java教程: Java教程 - 廖雪峰的官方网站 (liaoxuefeng.com)icon-default.png?t=L9C2https://www.liaoxuefeng.com/wiki/1252599548343744


异常的类别

Error表示严重的错误,程序对此一般无能为力,例如:

  • OutOfMemoryError:内存耗尽
  • NoClassDefFoundError:无法加载某个Class
  • StackOverflowError:栈溢出

Exception则是运行时的错误,它可以被捕获并处理, 分为两大类:

  • RuntimeException以及它的子类;
  • 非RuntimeException(包括IOException、ReflectiveOperationException等等)

不需要捕获的异常,包括Error及其子类,RuntimeException及其子类,

编译器对RuntimeException及其子类不做强制捕获要求,不是指应用程序本身不应该捕获并处理RuntimeException。是否需要捕获,具体问题具体分析

异常的捕获与抛出

多个catch语句只有一个能被执行。例如:

public static void main(String[] args) {
    try {
        process1();
        process2();
        process3();
    } catch (IOException e) {
        System.out.println(e);
    } catch (NumberFormatException e) {
        System.out.println(e);
    }
}

存在多个catch的时候,catch的顺序非常重要:子类必须写在前面。例如:

public static void main(String[] args) {
    try {
        ...
        ...
    } catch (IOException e) {
        System.out.println("IO error");
    } catch (UnsupportedEncodingException e) { // 永远捕获不到
        System.out.println("Bad encoding");
    }
}

UnsupportedEncodingException异常捕获不到,因为它是IOException的子类。当抛出UnsupportedEncodingException异常时,会被catch (IOException e) { ... }捕获并执行。

正确的写法是把子类放到前面:

public static void main(String[] args) {
    try {
        ...
        ...
    } catch (UnsupportedEncodingException e) {
        System.out.println("Bad encoding");
    } catch (IOException e) {
        System.out.println("IO error");
    }
}

打印出方法的调用栈 

通过printStackTrace()可以打印出方法的调用栈 

示例1:

public class Main {
    public static void main(String[] args) {
        try { 
            process1();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static void process1() {
        process2();
    }

    static void process2() {
        Integer.parseInt(null); // 会抛出NumberFormatException
    }
}

 

从下往上看, 调用层级关系

main()   process1()    process2() 

  Integer.parseInt(String)    Integer.parseInt(String, int)

查看Integer.java源码可知,抛出异常的方法代码如下:

 

示例2:

public class demo {
    public static void main(String[] args) {
        try {
            process1();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static void process1() {
        try {
            process2();
        } catch (NullPointerException e) {
            throw new IllegalArgumentException(e);
        }
    }

    static void process2() {
        throw new NullPointerException();
    }
}

 由Caused by: java.lang.NullPointerException可知,问题的根源在于Main.process2()方法抛出的NullPointerException

注意

  • 在代码中获取原始异常可以使用Throwable.getCause()方法。如果返回null,说明已经是“根异常”了
  • 捕获到异常并再次抛出时,一定要留住原始异常,否则很难定位第一案发现场
  • catch中抛出异常,不会影响finally的执行。JVM会先执行finally,然后抛出异常    (若是finally中抛出了异常, catch所捕捉到的异常便会丢失, 像是中途被调包了似的, 这叫做异常屏蔽)

自定义异常

(引用自自定义异常 - 廖雪峰的官方网站)

保持一个合理的异常继承体系

一个常见的做法是自定义一个BaseException作为“根异常”,然后,派生出各种业务类型的异常。

BaseException需要从一个适合的Exception派生,通常建议从RuntimeException派生:

public class BaseException extends RuntimeException {
}

其他业务类型的异常就可以从BaseException派生:

public class UserNotFoundException extends BaseException {
}

public class LoginFailedException extends BaseException {
}

...

自定义的BaseException应该提供多个构造方法:

public class BaseException extends RuntimeException {
    public BaseException() {
        super();
    }

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

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

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

上述构造方法实际上都是原样照抄RuntimeException。这样,抛出异常的时候,就可以选择合适的构造方法。通过IDE可以根据父类快速生成子类的构造方法。

原文地址:https://www.cnblogs.com/Knight02/p/15799058.html