概述:在程序设计和运行的过程中,发生错误是不可避免的,尽管我们去尽量的减少错误的产生,但是总会有我们没有预料到的错误。比如说,我们在程序开发中使用的if-else语句,其实这个就是处理异常的过程,但是这对于代码庞大的程序来说会有一些问题,比如说,代码臃肿:业务代码和异常处理代码放一起,程序员要花很大精力堵漏洞,程序员很难堵住所有“漏洞”。所以,java提供了异常处理机制来帮助程序员检查可能出现的错误,它将异常处理代码与业务代码分离,java将异常封装到一个类中,出现错误时,会抛出异常,这样保证了程序的可读性和可维护性。当然,异常处理机制也存在一些弊端,比如使用异常处理可能会降低程序的执行效率,增加语法复杂度等。
目录
一、异常的分类
java类库的每一个中都定义了异常类,所以这些类都是Throwable类的子类。Throwable类派生了两个子类,分别是Error类和Exception类,其中,Error类及其子类用来描述java运行系统中的内部错误以及资源耗尽的错误,这类错误比较严重。Exception类称为非致命性类,可以通过捕捉处理使程序继续执行。Exception类又可以根据错误发生的原因分为运行时异常和非运行时异常。
1、Error——系统错误
Error类及其子类通常用来描述java运行系统中的内部错误,该类定义了常规环境下不希望由程序捕获异常,例如ThreadDeath等,这些错误发生时,java虚拟机(JVM)一般会选择线程终止。
从图中我们可以看到显示的异常信息是"java.lang.Error",这说明这是一个系统错误,程序遇到这种错误通常会停止执行,并且,这类错误无法使用异常语句处理。
2、Exception——异常
Exception是程序可以处理的异常,这种异常主要分为运行时异常和非运行时异常,程序中应当尽可能去处理执行异常。
(1)、运行时异常
运行时异常是程序运行过程中产生的异常,它是RuntimeException类及其子类异常。这些异常一般是由程序员逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。下表罗列了 java提供了常见的RuntimeException异常。
异常类 | 说明 |
---|---|
ClassCastException | 类型转换异常 |
NullPointException | 空指针异常 |
ArrayIndexOutOfBoundsException | 数组下标越界异常 |
ArithmeticException | 算术异常 |
ArrayStoreException | 数组中包含不兼容的值抛出的异常 |
NumberFormatException | 字符串转换为数字抛出的异常 |
IllegalArgumentException | 非法参数异常 |
FileSystemNotFoundExcetion | 文件系统未找到异常 |
SecurityException | 安全性异常 |
StringIndexOutOfBoundsException | 字符串索引超出范围抛出的异常 |
NegativeArraySizeException | 数组长度为负异常 |
(2)、非运行时异常
非运行时异常是RuntimeException类及其子类异常以外的异常。这类异常是必须要处理的异常,如果不处理,程序就不能编译通过。
异常类 | 说明 |
---|---|
ClassNotFoundException | 未找到相应类异常 |
SQLException | 操作数据库异常类 |
IOException | 输入/输出流异常 |
TimeoutException | 操作超时异常 |
FileNotFoundException | 文件未找到异常 |
二、异常处理
1、捕捉处理异常
(1)、try...catch...finally代码块
try...catch代码块主要用来对异常进行捕捉并处理。在实际使用时,该代码块还有一个可选的finally代码块。标准语法如下:
try{
//程序代码块
}
catch(Exceptiontype e)
//对Exceptiontype的处理
}
finally{
//代码块
}
try代码块是可能发生异常的java代码;catch代码块在try代码块之后,用来激发被捕获的异常;finally代码块是异常处理结构的最后执行部分,无论程序是否发生异常,finally代码块中的代码都将被执行,所以在finally代码块中通常放一下释放资源、关闭对象的代码。
案例:
public class TryCatchFinally { //创建类
public static void main(String[] args) {
try { //try语句中包含可能出现异常的程序代码
String str = "Tom";
System.out.println(str + ",");
int age = Integer.parseInt("x");//数据类型转换
System.out.println(age+"岁了");
}catch(Exception e) { //catch代码块用来获取异常信息,Exception是try代码块传递给catch代码块的类型,e是对象名
e.printStackTrace(); //输出异常性质
}
finally {
System.out.println("program over");
}
}
}
运行结果如下:
我们可以看到,程序依然可以运行,没有因为异常而终止。当try代码块中的语句发生异常时,程序就会跳转到catch代码块中执行,执行完catch代码块中的程序代码后,将继续执行catch代码块后的其他代码块,而不会执行try代码块中发生异常语句后面的代码。
上面代码中,在catch代码块中使用了Exception对象的printStackTrace()方法输出了异常的栈日志。除了这个以外,还有三个常用方法:
(1)getMessage()方法:获取有关异常事件的信息
(2)toString()方法:获取异常的类型与性质
(3)printStackTrace()方法:获取异常事件发生时执行堆栈的内容
无论程序中有无发生异常,finally代码块中的代码一般情况下都会正常执行。除非以下三种情况:
(1)在finally代码块中发生异常
(2)在前面的代码中使用了System.exit() 退出程序
(3)程序所在的线程死亡
2、在方法中抛出异常
(1)、使用throws关键字抛出异常
throws关键字通常被应用在声明方法中,用来指定方法可能抛出的异常,多个异常可使用逗号分隔。
格式:
返回值类型 方法名(参数列表) throws 异常类型名{
//方法体
}
案例:
public class TestThrows {
static void test() throws ArrayIndexOutOfBoundsException{//定义方法并抛出数组下标越界异常
int[] arr = new int[2];
for(int i=0;i<10;i++)
{
arr[i]=i;
}
}
public static void main(String[] args) {
try {
test(); //调用方法
}catch (ArrayIndexOutOfBoundsException e) {
System.out.println("test()方法抛出异常");//输出异常信息
}
}
}
运行结果 :
指得注意的是:当使用throws为方法抛出异常时,如果子类继承父类,子类重写方法抛出的异常也要和原父类方法抛出的异常相同或是其异常的子类,除非throws 异常时RuntimeException。
(2)、使用throw关键字抛出异常
throw关键字通常用在方法体中“制造”一个异常,程序在执行到throw语句时立即终止,它后面的语句都不执行。throw语句通常用于程序出现某种逻辑错误时,由开发者主动抛出某种特定类型的异常。
格式:
throw new 异常类型名(异常信息)
案例:
public class TestThrow {
public static void main(String[] args) {
int num = 5;
int result;
for(int i=0;i<5;i++)
{
if(i==0)
{
throw new ArithmeticException("除数不能为0");//抛出算术异常
}
result = num/i;
System.out.println("result = "+result);
}
}
}
运行结果:
throw与throws的区别:
1、throws用在方法声明后面,表示抛出异常,由方法的调用者处理,而throw用在方法体内,用来制造一个异常,由方法体内的语句处理。
2、throws是声明这个方法会抛出这种类型的异常,以便使它的调用者知道要捕获这个异常,而throw是直接抛出一个异常实例。
3、throws表示出现异常的一种可能性,并不一定会发生,如果使用throw,就一定会产生某种异常
三、自定义异常
在程序中,可能会遇到使用java内置异常类也无法处理的异常情况,比如说,年龄为负等。这个时候就需要我们自定义异常类。自定义异常类只需从Exception类或它的子类派生一个即可,如果我们自定义异常类继承Exception类,则必须对其进行处理,如果不想处理,可以继承RuntimeException类。习惯上,自定义异常类应该包含两个构造器,一个默认构造器,一个包含有详细新的构造器。
案例:
public class CustomException {
public static void main(String[] args) {
TestAge t = new TestAge();
t.setAge(-10);
}
}
class TestAge{
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
if(age<0)
{
throw new MyException("年龄不能为负数");
}
this.age = age;
}
}
class MyException extends RuntimeException{//继承RuntimeException类,如果继承了Exception类,必须进行处理,就不能仅使用throw
public MyException() { //默认构造器
}
public MyException(String erms) { //带有详细信息的构造器,信息存储在erms中
super(erms);
}
}
四、异常的使用原则
1、不用过度使用异常。虽然通过异常可以增强程序的健壮性,但是如果过度使用不必要的异常处理,可能会影响程序的执行效率。
2、不要使用过于庞大的try...catch块,在一个try块中放置大量的代码,会造成try块中出现异常的可能性大大增加,也会使异常原因的分析困难加大。
3、避免使用catch(Exception e)。如果所有异常都采用相同的处理方式,将导致无法对不同异常的分情况处理;另外,这种捕获方式可能将程序中的全部错误、异常捕获,如果这是出现一个关键错误,可能会被忽略掉。
4、不要忽略捕捉到的异常,遇到异常要及时处理。
5、如果父类抛出多个异常,则覆盖方法必须抛出相同的异常或其异常的子类,不能抛出新的异常。