链接:异常处理 - 廖雪峰的官方网站

Java的异常

  • Java中,异常是一种class,因此它本身带有类型信息
  • Error表示严重的错误,程序对此一般无能为力,不需要捕获的异常
    • OutOfMemoryError:内存耗尽
    • NoClassDefFoundError:无法加载某个Class
    • StackOverflowError:栈溢出
  • Exception表示运行时的错误,可以被捕获并处理
    • 例子:
      • NumberFormatException:数值类型的格式错误
      • FileNotFoundException:未找到文件
      • SocketException:读取网络失败
      • NullPointerException:对某个null的对象调用方法或字段
      • IndexOutOfBoundsException:数组索引越界
    • RuntimeException以及它的子类,是否被捕获并处理视情况而定
    • RuntimeException(包括IOExceptionReflectiveOperationException等等),必须被捕获并处理
  • 捕获异常使用try...catch语句,把可能发生异常的语句放在try { … }中,然后使用catch捕获对应的Exception及其子类
  • 所有异常都可以调用printStackTrace()e.printStackTrace();)方法打印异常栈

捕获异常 try … catch … finally

  • 可以使用多个catch语句,分别捕获对应的Exception及其子类,但是只有一个catch语句被执行(自上到下匹配,一旦匹配成功则执行,不再继续往下匹配)
  • 因此,catch的顺序非常重要:子类必须写在前面,否则捕获到父类就不会再匹配到子类了
  • finally语句块保证有无错误都会执行

    1. public static void main(String[] args) {
    2. try {
    3. //正常语句体
    4. } catch (UnsupportedEncodingException e) { //异常类1,异常处理对象1
    5. System.out.println("Bad encoding"); //处理异常类1,异常处理对象1的语句
    6. } catch (IOException | NumberFormatException e) { //用 | 合并相同处理的异常
    7. System.out.println("Bad input");
    8. //或者使用 e.printStackTrace(); //所有异常都可以调用printStackTrace()方法打印异常栈
    9. } finally {
    10. System.out.println("END"); //最后执行的语句,有无错误都会执行
    11. }
    12. }
  • | 合并相同处理的异常

抛出异常 throw

  • 抛出异常分两步

    • 创建某个Exception的实例
    • throw语句抛出
      1. void process2(String s) {
      2. if (s==null) {
      3. throw new NullPointerException();
      4. }
      5. }
  • 子过程使用throw抛出异常,调用该子过程的上层调用方法使用try ... catch ... finally捕获异常并处理

  • 调用printStackTrace()e.printStackTrace();)可以打印异常的传播栈

自定义异常

  • 先自定义一个BaseException作为“根异常”,BaseException通常建议从RuntimeException派生

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

      1. public class BaseException extends RuntimeException {
      2. public BaseException() {
      3. super();
      4. }
      5. public BaseException(String message, Throwable cause) {
      6. super(message, cause);
      7. }
      8. public BaseException(String message) {
      9. super(message);
      10. }
      11. public BaseException(Throwable cause) {
      12. super(cause);
      13. }
      14. }
  • 其他业务类型的异常就可以从BaseException派生 ```java public class UserNotFoundException extends BaseException { }

public class LoginFailedException extends BaseException { }

  1. <a name="9ePJS"></a>
  2. # NPE 空指针异常 NullPointerException
  3. - `NullPointerException`是`RuntimeException`异常的子类
  4. - `NullPointerException`是代码逻辑错误,应尽早发现并修改代码,而不能用`catch`捕获来隐藏这种编码错误
  5. - `NullPointerException`的避免:
  6. - 使用空字符串`""`而不是默认的`null`
  7. ```java
  8. public class Person {
  9. private String name = ""; //而不是写成 private String name = null;
  10. }
  • 返回空字符串""、空数组而不是null

    public String[] readLinesFromFile(String file) {
    if (getFileSize(file) == 0) {
       // 返回空数组而不是null:
       return new String[0];
    }
    ...
    }
    
  • 如果调用方一定要根据null判断,比如返回null表示文件不存在,那么考虑返回Optional<T>,这样调用方必须通过Optional.isPresent()判断是否有结果

    public Optional<String> readFromFile(String file) {
    if (!fileExist(file)) {
       return Optional.empty();
    }
    ...
    }
    
  • 给JVM添加一个-XX:+ShowCodeDetailsInExceptionMessages参数可以输出异常的定位
    java -XX:+ShowCodeDetailsInExceptionMessages Main.java
    

断言 assert

public class Main {
    public static void main(String[] args) {
        int x = -1;
        assert x > 0;    //断言失败时抛出AssertionError,并退出程序
        System.out.println(x);
    }
}
  • 断言失败时会抛出AssertionError,导致程序结束退出。因此,断言不能用于可恢复的程序错误,只应该用于开发和测试阶段
  • 对于可恢复的程序错误,不应该使用断言,而应抛出异常并在上层捕获
  • JVM默认关闭断言指令,使用-enableassertions-ea参数启用断言
    java -ea Main.java
    

日志

  • 日志:在调试程序时,不再需要反复增删System.out.println()
  • 日志自动打印了时间、调用类、调用方法等很多有用的信息

    JDK Logging

  • JDK Logging包含7个级别,从高到低:

    • SEVERE
    • WARNING
    • INFO
    • CONFIG
    • FINE
    • FINER
    • FINEST
  • Logging默认级别是INFO,因此INFO级别以下的日志,不会被打印出来 ```java import java.io.UnsupportedEncodingException; import java.util.logging.Logger;

public class Main { public static void main(String[] args) { Logger logger = Logger.getLogger(Main.class.getName()); logger.info(“Start process…”); try { “”.getBytes(“invalidCharsetName”); } catch (UnsupportedEncodingException e) { // TODO: 使用logger.severe()打印异常 logger.severe(e.toString()); } logger.info(“Process end.”); } }

输出:

Oct 26, 2020 11:43:41 AM Main main INFO: Start process… Oct 26, 2020 11:43:41 AM Main main SEVERE: java.io.UnsupportedEncodingException: invalidCharsetName Oct 26, 2020 11:43:41 AM Main main INFO: Process end. ```

第三方日志库 Commons Logging