Java中的异常机制
10.1 异常类的继承关系与分类
从上图继承关系可知:
1.Throwable
是异常体系的根,它继承自 Object
。 Throwable
有两个体系: Error 和 Exception
, Error 表示严重的错误,程序对此一般无能为力,例如:
OutOfMemoryError
:内存耗尽(堆内存异常,数组空间开辟过大,程序中对象太多)NoClassDefFoundError
:无法加载某个ClassStackOverflowError
:栈溢出(函数递归)
2. Exception
则是运行时的错误,它可以被捕获并处理
。
某些异常是应用程序逻辑处理的一部分,应该捕获并处理
。例如:
NumberFormatException
:数值类型的格式错误 (将数字字符串进行解析)FileNotFoundException
:未找到文件SocketException
:读取网络失败
还有一些异常是程序逻辑编写不对造成的,应该修复程序本身
。例如:
NullPointerException
:对某个 null 的对象调用方法或字段ArrayIndexOutOfBoundsException
:数组索引越界
3.Exception
又分为两大类:
(1) RuntimeException
以及它的子类
;
(2)非 RuntimeException
(包括 IOException
、 ReflectiveOperationException
等等)
4.Java规定:
必须捕获
的异常,包括Exception 及其子类
,但不包括 RuntimeException 及其子类
,这种类型的异常称为Checked Exception。不需要捕获的异常
,包括Error 及其子类
,RuntimeException 及其子类
。
5.其他常见异常信息:
-
StringfIndexOutOfBoundsException
:字符串角标越界异常 角标不在字符串范围内 -
ArithmeticException
: 数学运算异常 非法的数学运算 -
ClassCastException
: 类型转换异常 将类进行错误的强制转换 -
InputMismatchException
:输入不匹配异常 在输入时输入非法数据 -
ParseException
:时间解析异常 非法的时间格式
10.2 捕获异常
Try —catch ----finally
try 块{
//可能会有异常发生的代码
}catch(Exception e ){
// 当有异常发生的时候,catch块被执行;当没有异常发生的时候 catch块就不执行了
}finally{
//不管异常是否发生都要执行
}
10.2.1 多catch语句
可以使用多个 catch 语句,每个 catch 分别捕获对应的 Exception 及其子类。JVM在捕获到异常后,会从上到下匹配 catch 语句,匹配到某个 catch 后,执行 catch 代码块,然后不再继续匹配。
即多个 catch 语句只有一个能被执行。例如:
public static void main(String[] args) {
try {
process1();
process2();
process3();
} catch (IOException e) {
System.out.println(e);
} catch (NumberFormatException e) {
System.out.println(e);
}
}
存在多个 catch 的时候, catch 的顺序非常重要:子类必须写在前面。
例如:
public static void main(String[] args) {
try {
process1();
process2();
process3();
} catch (IOException e) {
System.out.println("IO error");
} catch (UnsupportedEncodingException e) {
// 永远捕获不到
System.out.println("Bad encoding");
}
}
对于上面的代码,UnsupportedEncodingException 异常
是永远捕获不到的,因为它是IOException 的子类
。当抛出 UnsupportedEncodingException
异常时,会被 catch(IOException e) { ... }
捕获并执行。
正确的写法:把子类放到前面:
public static void main(String[] args) {
try {
process1();
process2();
process3();
} catch (UnsupportedEncodingException e) {
System.out.println("Bad encoding");
} catch (IOException e) {
System.out.println("IO error");
}
}
10.2.2 捕获多种异常
如果某些异常的处理逻辑相同,但是异常本身不存在继承关系,那么就得编写多条 catch 子句:
public static void main(String[] args) {
try {
process1();
process2();
process3();
} catch (IOException | NumberFormatException e) {
// IOException或 NumberFormatException
System.out.println("Bad input");
} catch (Exception e) {
System.out.println("Unknown error");
}
}
小结
:
使用 try … catch … finally 时:
多个 catch 语句的匹配顺序非常重要,子类必须放在前面;
finally 语句保证了有无异常都会执行,它是可选的;
一个 catch 语句也可以匹配多个非继承关系的异常。
10.2.3 try-catch-finally中的return
try块
中有return关键字
时,如果没有finally 则直接返回。try块
中有return 关键字
,catch块
也有return
;如果有
异常时,则作走catch块的return
;如果没有
异常则走try里return
。- 当
try块
中有return
,finally
中也有return
; 如果没有
异常发生走finally里的return
。 - 当
try块
中有return,catch
也有return,finally
还有return;没异常按照第3条走,如果有异常 则按照finally中的返回结果执行。
package Demo;
public class ExceptionDemo01 {
public static void main(String[] args) {
ExceptionDemo01 testDemo01 = new ExceptionDemo01();
ExceptionDemo01 test = testDemo01.get();
System.out.println(test);
}
@SuppressWarnings("finally")
public ExceptionDemo01 get() {
int a = 10;
int b = 0;
try{
int c = 10/0;
System.out.println("444");
return null;
}catch(Exception e){
try{
System.out.println("123");
}catch(Exception ex){
}
return this;
}finally{
System.out.println("我是一定要执行的");
return null;
}
}
}
*10.2.4 final finally finalize() 有什么区别?
*10.2.5 Thread. sleep()是否会抛出checked exception?(会)
答:
checked exception
:指的是编译时异常
,该类异常需要本函数必须处理的
,用try和catch处理,或者用throws抛出异常,然后交给调用者去处理异常。
runtime exception
:指的是运行时异常
,该类异常不必须本函数必须处理,当然也可以处理。
Thread.sleep()
抛出的InterruptException
属于checked exception
;IllegalArgumentException
属于Runtime exception
。
10.2.6 finnally可以单独和哪个一起使用?
try,可以是try-catch;try-catch-finnally;try-finnally。没有catch的时候一定要有finnally!
10.3 抛出异常和声明异常
throw
public static void main(String[] args) {
//抛出异常throw
String id;
System.out.println("请输入您的员工编号");
Scanner input = new Scanner(System.in);
id = input.next();
if(id.length() == 6){
System.out.println("你是一个合格的员工");
}else{
throw new IllegalArgumentException("你的id不符合要求,请联系上帝");
}
}
throws
声明异常 一般情况下用在你的方法头 。
package P36;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.text.ParseException;
public class ExceptionDemo06 {
public static void main(String[] args) {
}
}
class Fu {
public void show(){}
public void test(){}
public void haha() throws IOException, SQLException {}
public void xixi() throws IndexOutOfBoundsException{}
}
class Zi extends Fu {
//父类如果没有声明异常,则子类重写的函数不能声明编译时异常
//如果子类中真要有抛出编译时异常 只能try-catch
// public void show() throws ParseException {}
//父类如果没有声明异常,则子类重写的函数可以声明运行时异常
public void test() throws NullPointerException{}
//父类如果声明编译时异常,则子类重写的函数可以不声明编译时异常
//父类如果声明编译时异常,则子类重写的函数声明编译时异常 必须是父类异常或其子类异常或其子类异常的集合
public void haha() throws FileNotFoundException, InterruptedIOException, SQLWarning {}
//父类如果声明运行时异常,则子类重写的函数可以不声明运行时异常
//父类如果声明运行时异常,则子类重写的函数声明运行时异常 必须是父类异常或其子类异常或其子类异常的集合
public void xixi() throws ArrayIndexOutOfBoundsException,StringIndexOutOfBoundsException{}
}
*10.3.1 throw和throws的区别?(从关键字和用法说区别)
throws
用在函数头,throws
后跟的是异常类,可以跟多个;throw
位于函数体内部,后面跟的是异常对象。throws
用来声明异常,主要是声明非运行时异常
,做检查使用
。说明该功能下可能出现的问题,可以给出预先的处理方式;throw
用来抛出异常,用于抛出一些运行时异常,在代码中可以自定义逻辑,也可以抛出自定义的异常。throw
执行之后,后面的代码不再被执行,在代码中可以自定义逻辑,也可以抛出自定义的异常。throws
表现出的是异常的一种可能性,不一定会发生;throw
抛出了异常,执行了之后一定抛出了异常对象。- 两种方式都只能抛出或可能抛出异常,但是不会处理异常,处理异常
由函数上层调用处理
。
10.4 自定义异常
Exception,RunTimeException
用于在自己的框架中描述一些自定义的异常类,展示和Java原有的异常信息不同
实现方案:
要借助 Exception 和 RunTimeException
public class MyDaoException extends RuntimeException {
static final long serialVersionUID = -7034897190745766939L;
public MyDaoException() {
super();
}
public MyDaoException(String message) {
super(message);
}
public MyDaoException(Throwable cause) {
super(cause);
}
public MyDaoException(String message, Throwable cause) {
super(message, cause);
}
问题:接口中没有声明异常,而实现的子类覆盖方法时发生了异常,怎么办?
答:无法进行throws声明,只能catch的捕获。万一问题处理不了呢?catch中继续throw抛出,但是只能将异常转换成RuntimeException子类抛出。
10.5 一个代码
public static void main(String[] args) {
int[] arr = {1,2,3,4,5};
int num = getNumber(arr,10);
System.out.println(num);
}
private static int getNumber(int[] arr, int i) {
//return arr[i];//是由JVM来进行判断的
//模拟JVM的操作 用手动的判断模拟JVM自动的发现
if (arr == null) {
//产生一个空指针异常的问题对象
//用throw(抛出)关键 将产生的问题告知调用者
throw new NullPointerException();
//一旦上述抛出一个问题 此函数立马中断
//类似于return 正常结束(弹栈)
//throw非正常结束(中断 强制弹栈 并抛出问题给调用者)
}
if (i < 0 || i >= arr.length) {
//产生一个数组角标越界异常的问题对象
throw new ArrayIndexOutOfBoundsException();
//throw new StackOverflowError();
//PS 一旦出现错误 抛出问题即可 但是最好抛出最相关最精确的问题
}
return arr[i];
}
getNumber如果发生了问题是直接将问题抛给主函数的,但是主函数也没有处理这个问题,主函数接着将问题抛给调用者JVM。
JVM不帮你解决 结果就是程序中断了!
JVM-调用main-调用getNumber-getNumber出现异常-抛出给main-main没有处理-抛出传递给JVM-JVM直接给你中断