异常分类
Java中所有的异常都继承自java.lang.Throwable类。Throwable类有两个直接子类,Error类和Exception类:
-
Error——程序无法处理的严重错误,编译器不做检查,通常JVM会终止线程的动作
- Exception——它又分为运行时异常和编译期异常
运行时异常
运行时异常:RuntimeException——通常是由于逻辑错误产生的,常见的NullPointException,ClassCastException,IndexOutOfBoundsException等。运行时异常在默认情况下会得到自动处理,所以通常用不着捕获RuntimeException。RuntimeException是那些可能在Java虚拟机正常运行期间抛出的异常的超类,可能在执行方法期间抛出但未被捕获的RuntimeException的任何子类都无需在throws子句中进行声明。
运行时异常(RuntimeException)也叫非受检异常,主要的运行时异常有下列几种:
- ArithmeticException - 算术逻辑运算异常
- ArrayIndexOutOfBoundsException - 数组下标越界访问异常
- IndexOutOfBoundsException - 下标越界异常
- StringIndexOutOfBoundsException - 字符串截取下标越界
- ArrayStoreException - 数据存储异常,写数组操作时,对象或数据类型不兼容
- ClassCastException - 类型转换异常
- IllegalArgumentException - 方法参数无效异常
- IllegalThreadStateException - 非法线程状态异常,比如线程调用两次start方法
- IllegalMonitorStateException - 某一对象试图等待对象的监视器,然而本身没有指定的监视器的线程
- IllegalStateException - 无效状态异常
- NullPointerException - 空指针异常,这个最常见,比如访问了空数组、方法等
- NumberFormatException - 数据格式异常
- SecurityException - 安全异常
- IncompatibleClassChangeException - 不合法类变更异常
- OutOfMemoryException - 内存不足异常
- NoClassDefFoundException - 找不到类定义异常
- IncompatibleTypeException - 不合法类型异常
- UnsatisfiedLinkException - 无法连接异常
- InternalException - 系统内部异常
- EnumConstantNotPresentException - 枚举常量不存在异常
下面看一个不常见的ArrayStoreException异常:
private static void func(){
//创建一个初始容量大小为2的字符串数组
String[] a = new String[2];
//Object是所有类的父类,Object数组指向a
Object[] b = a;
a[0] = "hello";
System.out.println(b[0]);
//java.lang.ArrayStoreException: java.lang.Integer
//创建的数组已经是String类型了,再给它赋值Integer类型就会出现异常
b[1] = Integer.valueOf(42);
}
那么开发过程中分清这些异常有什么好处呢?当然是明确开发中可能发生的异常,并用合理的方式进行处理。比如说,
- 我们可以安装checkstyle等插件再加上合理的注解,如@NonNull。让IDEA警告代码可能出现崩溃的风险的地方。
- NullPointException通常不要去捕获该异常,而是通过判空校验的方法去解决。
- 在方法上加上@Transactional则表示该方法执行成功后自动提交,方法抛出RuntimeException及其子类时自动回滚。如果想要非运行时异常(如ParseException)也进行回归操作,则需要使用@Transactional(rollbackFor=Exception.class)。
编译期异常
编译期异常:NonRuntimeException——必须处理,否则程序编译无法通过,这类异常在编译时需要捕获,常见的有IOException,SQLException等
编译器异常也叫受检异常(CheckedException),除了RuntimeException以外的异常,都属于受检异常,它们都在java.lang库内部定义。Java编译器要求程序必须捕获或声明抛出这种异常。一个方法必须通过throws语句在方法的声明部分说明它可能抛出但并未捕获的所有CheckedException。
- java.lang.ClassNotFoundException
- java.io.FileNotFoundException
- java.io.IOException
- java.sql.SQLException
- java.net.SocketException
- java.lang.CloneNotSupportedException
- java.lang.IllegalAccessException
- java.lang.InterruptedException
- java.lang.NoSuchFieldException
- java.lang.NoSuchMetodException
对可容错处理的情况使用受检异常(Checked Exception),对编程错误使用运行时异常(Runtime Exception)。另外,有以下准则,
- 不要通过一个空的catch块忽略异常
- 方法抛出的异常,应该与本身的抽象层次相对应
- 在finally块中不要使用return、break或continue使finally块非正常结束
- 不要直接捕获受检异常的基类Exception,一般作为补充没有考虑到的异常
- 一个方法不应抛出超过5个异常
- 在Javadoc的@throws标签中记录每个抛出的异常及其条件
自定义异常
自定义异常:自定义异常是继承Exception还是RuntimeException由异常本身的特点决定,我们业务代码的ApiException是继承的RunTimeException
自定义异常通常是我们自己根据业务特定自定义的异常信息,然后通过统一的全局异常处理对其进行格式化,如何定义一个全局异常处理类可以参考文末链接1的2.3节。
异常信息打印
通常,我们在开发过程中会捕获异常并打印异常信息相应的日志,但是部分异常信息不适合打印并暴露出去,详细信息可以参考文末链接2,主要的敏感异常如下,
- FileNotFoundException(可能泄漏系统文件目录结构)
- SQLException(可能泄漏数据库结构,遭受SQL注入攻击)
- BindException(可能泄漏应用绑定的端口信息)
- ConcurrentModificationException(可能泄漏线程不安全的信息)
- InsufficientResourcesException(泄漏服务资源不足信息,可能遭遇DOS攻击)
- MissingResourceException(可能泄漏资源枚举信息)
- JarException(可能泄漏系统结构信息)
- NotOwnerException(可能泄漏Owner枚举信息)
- OutOfMemoryError(可能遭遇DOS攻击)
- StackOverflowError(可能遭遇DOS攻击)
参考链接:
1、Spring Boot 异常处理 – @ExceptionHandler示例 · HowToDoInJava 中文系列教程 · 看云