异常介绍
当程序运行过程中抛出异常后,程序就自动退出,下面的代码就不执行了。如果抛出异常的问题是个致命的问题,就会导致系统崩溃。所以java设计提出了一个异常处理机制来解决这个问题。
异常处理机制:如果程序员认为某段程序可能会出现异常,可以使用try{...}catch{...}来捕获异常。捕获到的异常可以自己需要的方式进行处理。
基本概念
在java语言中,将程序执行中发生的不正常情况称之为"异常"。开发过程中语法错误、编译错误不属于异常;逻辑错误可能导致运行时异常。。
程序运行过程中的异常分为两大类:
-
Error(错误):java虚拟机无法解决的严重问题,程序会崩溃。如:jvm系统内部错误、StackOverflowError栈溢出等。 -
Exception: 其它因编程错误或者偶然的外部因素导致的一般性问题,可以使用针对性的代码处理。如:空指针异常、试图读取不存在的文件、网络连接中断等。Exception也分为两大类:
-
编译异常
-
运行时异常
-
异常体系图

-
Throwable类是所有异常类的基类,它实现了Serializable接口。
-
Error类是所有Error异常类的基类,非运行时异常直接继承
Exception或其子类(但不继承RuntimeException)。这两个类同时继承了Throwable类。 -
运行时异常类都会继承
RuntimeException类,所以RuntimeException类是所有运行时异常类的基类。运行时异常类,编译器检查不出来。一般是指编译时程序逻辑错误,是程序员应该避免其出现的异常。所以一般不作处理,因为这样的类异常很普遍,若全处理可以对程序的可读性和运行效率产生影响。
-
编译时(非运行时)异常类都会继承
Exception类。编译时(非运行时)异常类是编译器要求必须处理的异常。
常见异常
1. 运行时异常(非受检,RuntimeException 及其子类)
编译器不强制处理,通常是程序员逻辑错误,应通过编码避免。
| 异常类 | 含义 | 典型触发场景 |
|---|---|---|
ArithmeticException |
算术运算异常 | 整数除以 0:int a = 1 / 0;(注意:1.0 / 0 得 Infinity,不抛) |
NullPointerException |
空指针异常 | 调用 null 对象的方法/字段、访问 null 数组元素、自动拆箱 null 包装类 |
ArrayIndexOutOfBoundsException |
数组下标越界 | int[] a = new int[3]; a[3] = 1; |
StringIndexOutOfBoundsException |
字符串下标越界 | "abc".charAt(5)、"abc".substring(2, 5) |
IndexOutOfBoundsException |
索引越界(父类) | 集合越界:list.get(10) |
NegativeArraySizeException |
数组长度为负 | new int[-1]; |
ArrayStoreException |
数组元素类型不兼容(存入的对象类型与数组声明类型不符) | Object[] a = new String[2]; a[0] = Integer.valueOf(1); |
ClassCastException |
类型转换异常 | 强制转换失败:Object o = "s"; Integer i = (Integer) o;(o == null 时不会 抛,结果为 null) |
NumberFormatException |
数字格式异常(字符串转数字失败) | Integer.parseInt("abc"); |
IllegalArgumentException |
方法接收到不合法参数 | Thread.sleep(-1)、new ArrayList<>(-1) |
IllegalStateException |
对象状态不适合当前操作 | Iterator 在 remove() 前未 next() |
IllegalMonitorStateException |
监视器状态错误 | 未持有对象锁 就调用 obj.wait()/notify()/notifyAll(),或未在 synchronized 块内 |
IllegalThreadStateException |
线程状态不适合当前操作 | 对已启动 的线程再次调用 start() |
UnsupportedOperationException |
不支持该操作 | Arrays.asList(...) 返回的固定大小 List 调用 add() |
ConcurrentModificationException |
并发修改异常 | 用 for-each 遍历集合时直接 list.remove()(应用 Iterator.remove()) |
SecurityException |
安全异常:安全管理器(SecurityManager)拒绝访问受限资源(文件、网络、反射、系统属性等) | 沙箱环境 / 启用 SecurityManager 时访问受限资源 |
OutOfMemoryError |
Error 而非 Exception:JVM 无法分配内存 |
无限创建大对象、堆内存耗尽;不要 catch 它,应排查内存泄漏 |
StackOverflowError |
Error:栈深度超限 |
无限递归(无终止条件) |
2. 受检异常(非运行时异常,Exception 的子类但不继承 RuntimeException)
编译器强制要求 :要么 try-catch 捕获,要么在方法签名上 throws 声明,否则编译不通过。
| 异常类 | 含义 | 典型触发场景 |
|---|---|---|
IOException |
I/O 操作异常(父类) | 读写文件失败、流已关闭、网络中断 |
FileNotFoundException |
文件未找到(IOException 子类) |
new FileInputStream("不存在.txt") |
EOFException |
输入未完成即遇文件结尾(IOException 子类) |
DataInputStream.readInt() 读到文件末尾仍继续读 |
ClassNotFoundException |
找不到指定类(类加载阶段) | Class.forName("com.xxx.NotExist");反射/驱动加载时类不在 classpath |
InterruptedException |
线程在 sleep/wait/join 中被中断 |
调用 thread.interrupt() 打断阻塞中的线程;捕获后应 Thread.currentThread().interrupt() 恢复中断标志 |
CloneNotSupportedException |
对象未实现 Cloneable 接口 却调用 clone() |
class A {} 后 new A().clone()(Object.clone() 是 protected 且检查接口) |
NoSuchMethodException |
反射时找不到指定方法 | clazz.getMethod("notExist")(普通方法调用编译期就检查,不会抛这个) |
IllegalAccessException |
反射访问 无权访问的成员(非 public 且未 setAccessible(true)) |
field.get(obj) 访问 private 字段 |
NoSuchFieldException |
反射找不到字段 | clazz.getField("notExist") |
InstantiationException |
反射创建实例失败 | clazz.newInstance() 作用于接口/抽象类/无无参构造 |
SQLException |
数据库访问异常 | 连接失败、SQL 语法错误 |
ParseException |
解析异常(java.text) |
new SimpleDateFormat("yyyy-MM-dd").parse("abc") |
异常处理
当程序发生异常时,分为两种方式处理异常:
try...catch...finally
java
try{
//代码可能有异常
}catch(Exception e) { //如果有异常,捕获到异常。可以写多个catch。
//当异常发生时,系统将异常封装成Exception 对象e,传递给catch
//得到异常对象后,程序员自己处理
}finally{//finally可以不写
//不管try代码块是否有异常发生,始终要执行finally。
//所以,通常将释放资源的代码,放在finally
}
注意细节:
- 如果异常发生了,
try语句块中的异常发生的语句之后的代码不会被执行。会直接进入到catch代码块中,执行完成之后,还会继续执行try...catch之外的代码。 - 不管
try代码块是否有异常发生,始终要执行finally。有极少数情况不执行:System.exit()/Runtime.halt()- JVM 崩溃(如 StackOverflowError 后再次出错、OOM 直接 kill 进程)
- 线程被强制终止(已废弃的
Thread.stop)
- 可以捕获多个异常即写多个
catch语句,要求子类在前面,父类异常在后面。如果发生异常,只会进入第一个匹配的 catch 分支。否则父类在前"导致"子类 catch 永远不可达 - 可以进行
try-finally组合使用,这种组合的意思是不做异常处理,但是要做别的一些事情。由于没有捕获异常,所以异常最终会throw给JVM,程序会直接崩溃。
一道练习题:
java
public class Exception2 {
public static void main(String[] args) {
System.out.println(method());
}
public static int method() {
int i = 1;
try {
i++; //2
String[] strings = new String[3];
if (strings[1].equals("tom")) {
System.out.println(strings[1]);
} else {
strings[3] = "light";
}
return 1;
} catch (ArrayIndexOutOfBoundsException e) {
return 2;
} catch (NullPointerException e) {
return ++i; //由于finally没有return语句,jvm底层会先将i保存到temp中,执行finally语句块,然后返回temp
} finally {
++i;
System.out.println("i=" + i);
}
}
}
throws
将异常抛出,由上层调用者来处理,最顶级的处理者就是JVM。

在方法声明中使用的throws语句可以声明抛出异常列表(列表中的异常是平级的),throws后面的异常类型可以是方法中产生的异常,也可以是它的父类。
使用细节:
子类重写父类的方法时,子类重写的方法所抛出的异常类型要么和父类抛出异常一致,要么为父类抛出的异常类型的子类型。(子类抛出的异常类型<=父类抛出的异常类型)
运行时异常根本不需要 throws 声明,编译器不检查;若未处理会沿调用栈自动向上传播,最终由 JVM 处理(终止当前线程)。
自定义异常
当程序中出现了某些"错误",但该错误信息并没有在Throwable子类中描述处理,这个时候可以自己设计异常类,用于描述该错误信息。
步骤:
1)定义类:自定义异常类名(程序员自己写)继承Exception或RuntimeException
2) 如果继承Exception,属于编译异常。如果继承RuntimeException,属于运行异常(一般来说,继承RuntimeException)
- 一般使用throw关键字抛出异常。
java
//测试类
public class CustomException {
public static void main(String[] args) {
ThrowAgeException ex = new ThrowAgeException();
try{
ex.throwsExceptionMethod();
}catch (AgeException me){
System.out.println("AgeException:"+me.getMessage());
}
}
}
//抛出异常类
class ThrowAgeException {
public void throwsExceptionMethod() {
int age = 180;
if (!(age > 20 && age <= 120)) {
throw new AgeException("年龄不合法");
}
}
}
//自定义类
class AgeException extends RuntimeException{
private String message;
public AgeException(String message) {
super(message);
this.message = message;
}
@Override
public String getMessage() {
return this.message;
}
}
throws和throw区别

练习题:说出下面程序可能出现的异常
java
public static void main(String[] args) {
if(args[4].equals("john")){
System.out.println("AA");
}else{
System.out.println("BB");
}
Object o= args[2];
Integer i= (Integer)o;
}
1.如果args为空,数组越界异常、空指针异常。
2.Integer i = (Integet)o会出现类型转换异常。