异常的引入 |
说起异常,我们来看看异常结构树。java把所有的非正常情况分为两种,Error(错误)和Exception(异常)。其中的Exception就是咱们今天要讲的异常,对其进行处理就是异常处理。
(1)Error错误
Error是一般和虚拟机相关的问题,将导致应用程序中断。包括系统崩溃,虚拟机错误,动态链接失败等。这种错误无法恢复也无法捕获,无法在代码中对其进行“处理”。比如如果出现内存溢出的错误,可能是因为堆内存设置的过小 ,那我们可以通过增大堆内存来解决这个问题。这个和代码无关,属于JVM调优方面的问题。
(2)Exception异常
异常分为Checked异常和Runtime异常。
- Runtime异常
所有的RuntimeException类及其子类的实例被称为Runtime异常,即上图中的粉色部分。此异常无需显式声明抛出,如果需要捕获,可以使用try…catch块。大部分时候推荐使用此异常。
- Checked异常
其余的异常实例被称为Checked异常。Checked异常是java独有的,要求必须显示处理
此异常。如果没有对其进行处理,则在编译时会发生错误。处理方式有两种:try…catch和throws。
try…catch…finally |
try
{
//业务实现代码,这些代码可能引发异常
}
catch (IndexOutOfBoundsException ie)
{
//如果发生了数组越界异常,应该这样处理
}
catch (Exception e)
{
//如果发生了异常,应该这样处理
}
finally
{
//用于回收在try块中打开的物理资源
}
异常处理就是对业务实现代码进行处理,其一旦抛出异常,catch块就将此异常捕获。避免因为程序遇到问题而停止。其中,只有try是必选的,其余两个可选。下面分别说说各部分的作用。
(1)try
try后面放置的是实现业务逻辑的代码,我们的异常处理就是对这段代码进行了“保护处理”。
(2)catch
catch后面放置的是捕获的各种异常。常见的错误如下:
错误 | 解释 |
---|---|
IndexOutOfBoundsException | 数组越界异常 |
NumberFormatException | 数字格式异常 |
ArithmeticException | 除0异常 |
NullPointerException | 空指针异常 |
在catch块的使用中,有几个原则需要我们注意一下:
-
a、应先处理小异常,再处理大异常
像文章开头的实例,只有不符合数组越界异常,才会到达其父类“其它异常”处。如果两个catch块对调,则代码永远都不会到IndexOutOfBoundsException处。可以和“河北人”、“中国人”类比。
-
b、可以使用多异常捕获
在Java7以后,我们可以将多个异常放在一个catch块里。示例如下,只是其中的异常变量ie有隐式的final修饰,不能对其进行重新赋值。
try {}
catch (IndexOutOfBoundsException | NumberFormatException | ArithmeticException ie)
{
System.out.println("程序发生了数组越界、数字格式异常、算术异常之一");
}
catch (Exception e)
{
System.out.println("未知异常");
}
(3)finally
finally用于将数据库连接、网络连接、磁盘文件等物理资源进行关闭。不管是走try块还是catch块,最后都会走finally块。给我印象最深刻的一个应用场景是,在多线程的同步锁中,需要在finally块里释放锁。
FileInputStream fis=null;
try{
fis=new FileInputStream("a.txt");
}
finally{
if(fis!=null)
{
//关闭磁盘文件
fis.close();
}
}
为了避免代码的臃肿,try块有了自动关闭资源的功能,从此可以不用finally了。但对try块有规定,其资源实现类必须实现AutoCloseable或Closeable接口。
//声明和初始化都要在括号里
try( FileInputStream fis=new FileInputStream("a.txt") )
{
//其它代码
}
throws和throw |
(1)throws
throws和try…catch块的作用是一样的,都是用于处理异常,使用时二者选一即可。两者的区别在于,如果在定义方法时,使用throws声明抛出异常,则出现问题后会将该异常抛给上一级,自己不进行处理。
throws的使用规则如下:
- 如果一个方法调用了另一个带throws声明的方法,那此方法要么放在try块里,要么也声明throws。因为throws是将异常抛给上一级,那上一级肯定得进行处理。
- 儿子抛出的异常不能比老子大。子类方法声明抛出的异常类型应该是父类方法声明抛出的异常类型的子类或相同。
throws的使用举例如下:
public class ThrowsTest{
public static void main(String[] args) throws IOException{
FileInputStream fis=new FileInputStream("a.txt");
}
}
(2)throw
try…catch…finally和throws用于当程序出现问题时,系统自动抛出异常。throw用于出现与业务需要不符时,程序自行抛出异常。一个是被动,一个是主动。
使用举例如下:
if(age<0)
{
Exception e = new Exception();//创建异常对象
throw e;//抛出异常
}
System.out.println(age);
(3)两者区别
通常在一个方法(类)的声明处通过throws声明可能抛出的异常信息,而在方法(类)内部通过throw声明一个具体的异常信息。