异常
- 1. 异常概念
- 程序开发中常见异常
- 2. 异常的体系结构
- 2.1 异常体系核心父类
- 异常的另外划分模式
- 3. 异常的处理流程
- 3.1 异常的捕获并处理try-catch-finally代码块
- 组合一:try catch
- 组合二:finally代码块
- finally不执行的特殊情况
- 3.2 throw 和 throws
- 3.3 自定义异常
1. 异常概念
异常:程序执行过程中的不正常行为。异常是发生在程序执行过程中,遇到某些代码执行不下去,称之为异常。语法的错误并不属于异常,在编译阶段就会进行报错。
程序开发中常见异常
- 越界异常
索引下标非法:访问了数组中不存在的位置,越界异常
int[] num=new int[3];
System.out.println(num[3]);
- NPE空指针异常
通过一个值为null的引用调用成员方法/属性
int[] num=new int[3];
num=null;
System.out.println(num[1]);
- 类型转换异常
发生在两个毫无关系的类之间对象的转换(要发生向下转型首先得发生向上转型)
int[] num=new int[3];
num=null;
System.out.println(num[3]);
异常的出现是具有逻辑顺序的,面对空指针异常和越界异常,在程序运行时,只要碰到异常,通常会选择第一个异常进行抛出,终止程序运行。
2. 异常的体系结构
在Java中,异常也是类,一切皆对象。程序所抛出的异常,抛出的就是异常对象,只不过该对象是由JVM产生并返回给程序的!
2.1 异常体系核心父类
- Error:JVM无法解决的严重问题,当出现Error类问题,程序是无法解决的,只能退出。
StackOverFlowError(栈溢出) OutMemoryError(堆溢出) - Exception:异常产生后,程序员可以通过异常的处理流程来解决此类问题,使得程序能够继续执行。
异常的另外划分模式
- 受查异常/编译时异常
在程序编译时必须显示进行异常处理的异常,称为受查异常。受查异常若不进行处理,则程序会卡在编译阶段,无法运行。
例如:IOException、ClassNotFoundException、SqlException…除了非受查异常之外的都是受查异常。
以clone()方法为例:
- 非受查异常/运行时异常
程序编译时没有产生任何错误,但是程序执行时发生的异常称为非受查异常。
例如:Error、RuntimeException以及其子类(Arithmetic Exception、ClassCastException、IlegalArgumentException…)、NPE问题,属于程序执行起来出现的异常,非受查异常/运行时异常
3. 异常的处理流程
异常处理的5个主要关键字:try catch finally throw throws
3.1 异常的捕获并处理try-catch-finally代码块
try{
//可能产生的异常放在try代码块中
}[catch(异常类型 异常对象)][0...N]{
//若捕获到了相应类型的异常对象,如何处理
//放在catch代码块中
}[finally]{
//无论是否有异常产生或者是否处理异常,最终一定会执行的代码放在finally代码块中
//finally中的代码块一定会被执行到
}
组合一:try catch
try{
}catch(异常类型 异常对象){
}catch....{
}
a. 当没有异常产生时,不走catch块,程序正常执行结束
b. 当异常产生,且被正确的捕获到,则走相应的catch代码块,try代码块中从出现异常之后的代码不再执行;但是异常体系之后的代码会正常执行!
有异常处理之后,保证程序抛出异常之后,正确捕获该异常,就可以使得异常体系结构之后的代码继续执行。
c. 关于catch
块捕获异常的说明
-
当try中可能会产生多种异常时,可以使用多个代码块进行捕获;
-
也可以捕获异常的父类,通过向上转型来进行异常捕获(不推荐,具体异常不明确,只要不是error,都可以进行捕获)
-
输出异常产生的原因及位置,调用异常对象的
printStackTrace()
方法,异常出现时,由JVM产生异常对象并返回给程序。
try{
//异常代码
}catch(Exception e){
e.printStackTrace();
}
- 若catch代码块有多个分支,且多个分支有父子关系的情况,一定是子类异常的捕获要写在父类异常捕获之前。
try{
//异常代码
num=null;
System.out.println(num[100]);
System.out.println("其他代码");
}catch(NullPointerException e){
System.out.println("捕获到空指针异常");
e.printStackTrace();
}catch(Exception e){
e.printStackTrace();
}
System.out.println("异常之后代码");
- 多个catch块只会走一个,从上至下,匹配多个catch块,直至碰见第一个满足的catch代码块就进入,其他的代码块不再执行!跳转至异常体系之后的代码执行。
组合二:finally代码块
try{
}catch(异常类型 异常对象){
}finally{
无论是否产生异常,都会执行的代码块
}
- 未捕获异常:当产生异常后,没有被捕获的情况下,程序需要退出,程序退出之前,finally代码块需要先执行,再退出程序。
- 对异常进行捕获后:当try catch finally都有返回值时,使用catch对异常进行捕获,程序此时能够正常执行,在方法结束之前,先执行finally中的代码块,再进行返回值return。
- 无论是否产生异常,且是否具有返回值,finally中的代码块一定是会执行的!
- 位置1和位置2的代码有什么区别?
当有异常产生时,1,2都会执行,return 3;
如果没有异常产生,catch中的语句不会执行,执行finally之后,直接return 1从try中结束调用,不会执行2对应的代码。
总结:
所有关于资源关闭的操作,都会放在finall代码块中,文件的关闭,数据库连接的关闭,统一都放在finally代码块中,确保该资源一定会被关闭。
- 若finally有return语句,无论是否有异常产生,都会返回finally的返回值,因此一般不在finally中写return语句。
finally不执行的特殊情况
正常编码,finally一定会正常执行;若出现导致JVM进程直接退出的情况,finally才不会执行,eg:System.exit(0);
3.2 throw 和 throws
若出现异常,不会进行处理,将异常抛回给调用者处理。
- throws:用在方法声明上,明确表示该方法可能产生某些异常,但是该方法不处理,若出现异常,将异常对象抛回给调用者处理
若在整个调用过程中没有一个位置进行异常处理,最终这个异常对象就会抛回给JVM,整个程序退出。
关键点:
(1)throws 关键字在方法列表之后进行定义;
(2)throws 可以抛出多个异常,多个异常使用‘,’
进行分隔,若有父子关系,只需要抛出父类异常即可;
(3)throws 抛出的必须是Exception以及其子类。
特殊说明:
若throws抛出的是受查异常,则调用者必须进行显式的异常处理(要么使用try catch捕获,要么继续通过throws向上抛出)
clone抛出CloneNotSupportedException (受查异常) 代码示例
- throw:用在方法内部,程序员在出现异常时,自己产生异常对象并向外抛出,(原来异常对象是由JVM产生,现在程序员自己产生异常对象),一般搭配自定义异常使用。
手动产生非受查异常,编译不报错,程序执行到此,相当于JVM产生了异常对象,可以使用异常的处理流程进行处理
对于异常未作处理,相当于throws向外抛出的情况
使用try catch对自己产生的异常进行捕获处理
总结:
无论是哪种方式产生的对象(JVM产生还是自己throw new的对象)与异常产生的方式无关。
是否需要进行显式处理,需要判断异常对象是受查异常还是非受查异常;只要是受查异常就必须进行显式异常处理!!!
3.3 自定义异常
以简单用户登录为例:
public class MyException {
static String userName="张三";
static String passWord="123";
public static void main(String[] args) throws UserNameException {
Scanner sc=new Scanner(System.in);
System.out.println("请输入您的用户名:");
String user=sc.nextLine();
System.out.println("请输入您的密码:");
String pass=sc.nextLine();
login(user,pass);
}
public static void login(String userName,String passWord) throws UserNameException {
if(!userName.equals(MyException.userName))
{
//抛出用户名错误异常
throw new UserNameException("用户名错误!");
}
if(!passWord.equals(MyException.passWord))
{
//抛出密码错误异常
throw new PassWordException("密码错误");
}
System.out.println("登录成功!");
}
}
//用户名属于受查异常
class UserNameException extends Exception{
//只需要提供构造方法即可
public UserNameException(String msg){
super(msg);
}
}
//密码错误属于非受查异常
class PassWordException extends RuntimeException{
public PassWordException(String msg){
super(msg);
}
}