1. 异常概述
异常:在 Java 语言中,将程序执行中发生的不正常情况称为 "异常" (开发过程中的单词拼写错误、语法错误和逻辑错误不是异常,这是你不正常)。Java程序在执行过程中所发生的异常事件可分为两类:
- Error:Java 虚拟机无法解决的严重问题。如:JVM 系统内部错误、资源耗尽等严重情况
public class ErrorTest { public static void main(String[] args) { // java.lang.StackOverflowError main(args); // java.lang.OutOfMemoryError Integer[] arr = new Integer[1024*1024*1024]; } }
- Exception:其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理,如:
- 空指针访问
- 试图读取不存在的文件
- 网络连接中断
- 数组角标越界
- ...
2. 异常体系结构
- 运行时异常
- 是指编译器不要求强制处置的异常。一般是指编程时的逻辑错误,是程序员应该积极避免其出现的异常。
java.lang.RuntimeException
及它的子类都是运行时异常。 - 对于这类异常,可以不作处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响。
- 是指编译器不要求强制处置的异常。一般是指编程时的逻辑错误,是程序员应该积极避免其出现的异常。
- 编译时异常
- 是指编译器要求必须处置的异常。即程序在运行时由于外界因素造成的一般性异常。编译器要求 Java 程序必须捕获或声明所有编译时异常。
- 对于这类异常,如果程序不处理,可能会带来意想不到的结果。
3. 常见异常
- 运行时异常 // 继承自 java.lang.RuntimeException
- ClassCastException
- ArrayIndexOutOfBoundsException
- NullPointerException
- ArithmeticException
- NumberFormatException
- InputMismatchException
- ...
- 编译时异常
- java.io.IOExeption
- FileNotFoundException
- EOFException
- java.lang.ClassNotFoundException
- java.lang.InterruptedException
- java.io.FileNotFoundException
- java.sql.SQLException
- java.io.IOExeption
4. 异常处理方式
Java 提供的是异常处理的抓抛模型。
Java 程序的执行过程中如出现异常,会生成一个异常类对象, 该异常对象将被提交给 Java 运行时系统,这个过程称为抛出(throw) 异常。如果一个方法内抛出异常,该异常对象会被抛给调用者方法中处理。如果异常没有在调用者方法中被处理,它继续被抛给这个调用方法的上层方法。这个过程将一直继续下去,直到异常被处理。 这一「向上抛/处理」的过程称为捕获(catch) 异常。
如果一个异常被向上抛到了 main() 方法,且 main() 也不处理,则程序将运行终止。程序员通常只能处理 Exception,而对 Error 无能为力。
4.1 try-catch-finally
捕获异常的第 1 步是用 try{...}
选定捕获异常的范围,将可能出现异常的代码放在 try{...}
中。补充:try{...}
中定义的变量,只在此块内有效。
在 catch代码块
中是对异常对象进行处理的代码。每个 try{...}
可以伴随一个或多个 catch代码块
,用于处理可能产生的不同类型的异常对象。
捕获异常的最后一步是通过 finally语句
为异常处理提供一个统一的出口,使得在控制流转到程序的其它部分以前,能够对程序的状态作统一的管理。
不论在 try 代码块中是否发生了异常事件,catch 语句是否执行/catch 语句是否有异常/catch 语句中是否有 return,finally 块中的语句都会被执行。
如果明确知道产生的是何种异常,可以用该异常类作为 catch 的参数;也可以用其父类作为 catch 的参数。比如,可以用 ArithmeticException 类作为参数的地方,就可以用 RuntimeException 类作为参数,或者用所有异常的父类 Exception 类作为参数。但不能是与 ArithmeticException 类无关的异常,如 NullPointerException(catch 中的语句将不会执行)。
要注意的是,如果异常类型有子父类关系,则要求子类一定声明在父类的上面;否则,报错。
常用的异常对象处理的方法:
getMessage()
:获取异常信息,返回字符串printStackTrace()
:这个方法将返回一个由栈轨迹中的元素所构成的数组,其中每一个元素都表示栈中的一帧。元素 0 是栈顶元素,并且是调用序列中的最后一个方法调用(这个 Throwable 被创建和抛出之处)。数组中的最后一个元素和栈底是调用序列中的第一个方法调用.
开发中,由于运行时异常(RuntimeException 或其子类) 比较常见,这些类的异常的特点是:即使没有使用 try 和 catch 捕获,Java 自己也能捕获,并且编译通过 (但运行时会发生异常使得程序运行终止)。所以通常不针对运行时异常编写 try-catch 代码。
但针对编译时异常(非运行时异常),则必须要考虑异常的处理,你不处理,编译就报错,继而就生不成字节码文件,何谈运行。使用 try-catch-finally
处理编译时异常,使得程序在编译时不再报出错误,但是运行时仍可能抛出异常;相当于我们使用 try-catch-finally 将一个编译时可能出现的异常,延迟到运行时出现。也就是说,我们必须处理编译时异常,将异常进行捕捉,转化为运行时异常。
3.2 抛出
方法声明 [+ throws + 异常类型]
指明此方法执行时,可能会抛出的异常类型;一旦方法体执行,出现异常,仍会在异常代码处生成一个异常类的对象,若此对象满足 throws 后的异常类型,就会被抛给方法调用者。异常代码后续的代码,就不会再执行。
try-catch-finally
真正的将异常给处理掉了;throws
只是将异常抛给方法调用者,并没有真正将异常处理掉。
子类重写的方法抛出的异常类型不大于父类被重写方法抛出的异常类型,最小可以不抛出任何异常。多态时,catch(父类抛出的异常类型),如果子类抛出的异常比父类大,这个 catch 也抓不住这个异常呀。
同理,如果父类被重写方法没有 throws
,则子类重写方法若有异常,只能自己内部通过 try-catch
方式处理,不能 throws
。
执行的方法 a 中,先后调用了另外几个方法,这几个方法是递进关系执行的。建议这几个方法使用 throws
的方式进行处理。而方法 a 则考虑使用 try-catch
方式进行处理。
5. 手动抛出异常:throw
Java 异常类对象除在程序执行过程中出现异常时由系统自动生成并抛出,也可根据需要使用人工创建并抛出。可以抛出的异常必须是 Throwable 或其子类的实例。
异常对象的产生:① 系统自动在异常代码处生成的异常对象 ② 手动的生成一个异常对象,并抛出(throw)
别和 throws
搞混了,throws
声明在方法的声明处,体现的是 [异常的一种处理方式],throw
存在于方法体内,体现的是 [异常对象的生成]。
6. 用户自定义异常
一般地,用户自定义异常类都是 RuntimeException
的子类。自定义异常类通常需要编写几个重载的构造器:
自定义异常需要提供serialVersionUID
static final long serialVersionUID = -7034897190745766939L;
自定义的异常通过 throw 抛出;自定义异常最重要的是异常类的名字,当异常出现时,可以根据名字判断异常类型。
public class MyException extends RuntimeException {
static final long serialVersionUID = -7034897190745766939L;
public MyException() {}
public MyException(String msg) {
super(msg);
}
}