Java异常处理基础知识笔记:异常处理机制、异常继承关系、捕获异常、抛出异常、异常的传播、异常调用栈、自定义异常、第三方日志库

一、Java中的异常

1、Java内置了一套异常处理机制,总是使用异常来表示错误。异常是一种class,因此它本身带有类型信息。异常可以在任何地方抛出,但只需要在上层捕获,这样就和方法调用分离了

2、Java异常的继承关系

  从继承关系可知:Throwable是异常体系的根,它继承自ObjectThrowable有两个体系:ErrorExceptionError表示严重的错误,程序对此一般无能为力。如:

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

  而Exception则是运行时的错误,它可以被捕获并处理。某些异常是应用程序逻辑处理的一部分,应该捕获并处理。例如:

  • NumberFormatException:数值类型的格式错误
  • FileNotFoundException:未找到文件
  • SocketException:读取网络失败

  还有一些异常是程序逻辑编写不对造成的,应该修复程序本身。例如:

  • NullPointerException:对某个null的对象调用方法或字段
  • IndexOutOfBoundsException:数组索引越界

  Exception又分为两大类:

(1)RuntimeException以及它的子类;

(2)非RuntimeException(包括IOExceptionReflectiveOperationException等等)

3、Java规定:

  • 必须捕获的异常,包括Exception及其子类,但不包括RuntimeException及其子类,这种类型的异常称为Checked Exception。

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

4、捕获异常使用try...catch语句,把可能发生异常的代码放到try {...}中,然后使用catch捕获对应的Exception及其子类

5、在方法定义的时候,使用throws Xxx表示该方法可能抛出的异常类型。调用方在调用的时候,必须强制捕获这些异常,否则编译器会报错。

6、只要是方法声明的Checked Exception,不在调用层捕获,也必须在更高的调用层捕获。所有未捕获的异常,最终也必须在main()方法中捕获,不会出现漏写try的情况。这是由编译器保证的。main()方法也是最后捕获Exception的机会。

7、不推荐捕获了异常但不进行任何处理。所有异常都可以调用printStackTrace()方法打印异常栈,这是一个简单有用的快速打印异常的方法。

二、捕获异常

1、可以使用多个catch语句,每个catch分别捕获对应的Exception及其子类。JVM在捕获到异常后,会从上到下匹配catch语句,匹配到某个catch后,执行catch代码块,然后不再继续匹配。简单地说就是:多个catch语句只有一个能被执行。

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

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

  对于上面的代码,UnsupportedEncodingException异常是永远捕获不到的,因为它是IOException的子类。当抛出UnsupportedEncodingException异常时,会被catch (IOException e) { ... }捕获并执行。因此,正确的写法是把子类放到前面。

3、finally语句 —— finally是用来保证一些代码必须执行的

  无论是否有异常发生,如果我们都希望执行一些语句,例如清理工作,怎么写? —— Java的try ... catch机制还提供了finally语句,finally语句块保证有无错误都会执行。注意finally有几个特点:

(1)finally语句不是必须的,可写可不写;

(2)finally总是最后执行。

4、捕获多种异常:如果某些异常的处理逻辑相同,但是异常本身不存在继承关系,那么就得编写多条catch子句,因为处理IOExceptionNumberFormatException的代码是相同的,所以我们可以把它两用|合并到一起。

public static void main(String[] args) {
    try {
    } catch (IOException | NumberFormatException e) { // IOException或NumberFormatException
        System.out.println("Bad input");
    } catch (Exception e) {
        System.out.println("Unknown error");
    }
}

三、抛出异常

1、异常的传播:当某个方法抛出了异常时,如果当前方法没有捕获异常,异常就会被抛到上层调用方法,直到遇到某个try ... catch被捕获为止。

2、如何抛出异常?参考Integer.parseInt()方法,抛出异常分两步:实际上,绝大部分抛出异常的代码都会合并写成一行:throw new NullPointerException();

// 1、创建某个Exception的实例
NullPointerException e = new NullPointerException();

// 2、用throw语句抛出
throw e

3、异常屏蔽:在finally里抛出异常后,原来在catch中准备抛出的异常就“消失”了,因为只能抛出一个异常。没有被抛出的异常称为“被屏蔽”的异常(Suppressed Exception)。

四、自定义异常

1、Java标准库定义的常用异常包括

Exception
│
├─ RuntimeException
│  │
│  ├─ NullPointerException
│  │
│  ├─ IndexOutOfBoundsException
│  │
│  ├─ SecurityException
│  │
│  └─ IllegalArgumentException
│     │
│     └─ NumberFormatException
│
├─ IOException
│  │
│  ├─ UnsupportedCharsetException
│  │
│  ├─ FileNotFoundException
│  │
│  └─ SocketException
│
├─ ParseException
│
├─ GeneralSecurityException
│
├─ SQLException
│
└─ TimeoutException

2、在一个大型项目中,可以自定义新的异常类型,但是,保持一个合理的异常继承体系是非常重要的。一个常见的做法是自定义一个BaseException作为“根异常”,然后,派生出各种业务类型的异常。

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

public class BaseException extends RuntimeException {
}
// 其他业务类型的异常就可以从BaseException派生:
public class UserNotFoundException extends BaseException {
}

public class LoginFailedException extends BaseException {
}

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

5、小结:

  抛出异常时,尽量复用JDK已定义的异常类型;

  自定义异常体系时,推荐从RuntimeException派生“根异常”,再派生出业务异常;

  自定义异常时,应该提供多种构造方法。

五、NullPointerException

  NullPointerException即空指针异常,俗称NPE。如果一个对象为null,调用其方法或访问其字段就会产生NullPointerException,这个异常通常是由JVM抛出的。

  指针这个概念实际上源自C语言,Java语言中并无指针。我们定义的变量实际上是引用,Null Pointer更确切地说是Null Reference,不过两者区别不大。

六、使用JDK Logging

  日志是为了替代System.out.println(),可以定义格式,重定向到文件等;

  日志可以存档,便于追踪问题;

  日志记录可以按级别分类,便于打开或关闭某些级别;

  可以根据配置文件调整日志,无需修改代码;

  Java标准库提供了java.util.logging来实现日志功能。

七、第三方日志库

1、Commons Logging 与 Log4j

  通过Commons Logging实现日志,不需要修改代码即可使用Log4j;

  使用Log4j只需要把log4j2.xml和相关jar放入classpath;

  如果要更换Log4j,只需要移除log4j2.xml和相关jar;

  只有扩展Log4j时,才需要引用Log4j的接口(例如,将日志加密写入数据库的功能,需要自己开发)。

2、SLF4J和Logback

  SLF4J和Logback可以取代Commons Logging和Log4j;始终使用SLF4J的接口写入日志,使用Logback只需要配置,不需要修改代码。

posted @ 2021-05-25 18:31  古兰精  阅读(803)  评论(0编辑  收藏  举报