Java 异常的处理

本文最后更新于:2024年3月18日 凌晨

Java 异常的处理

  • 进行异常处理必须使用try程序块,将可能产生异常的放在try中,当JVM执行过程中发现了异常,会立即停止执行后续代码,然后开始查找异常处理器,对try后面的catch块按次序进行匹配检查,一旦找到一个匹配者,则执行catch块中的代码,不再检查后面的catch块,如果try块中没有异常发生,则程序执行过程中将忽略后面的catch块。
  • 以下为异常处理语句格式:
1
2
3
4
5
6
7
8
9
try{
语句块;
}
catch(异常类名参变量名){
语句块;
}
finally{
语句块;
}

说明

  • try语句块用来启动Java的异常处理机制,一个try可以引导多个catch块。
  • 异常发生后,try块中的剩余语句将不再执行。
  • 异常对象是依靠以catch语句为标志的异常处理语句块来捕捉和处理的,catch部分的语句块中的代码执行的条件是,首先在try块中发生了异常,其次是异常的类型与catch要捕捉的一致,在此情况下,运行系统会将异常对象传递给catch中的参变量,在catch块中可以通过该对象获取异常的具体信息。
  • 在该结构中,可以无finally部分,但如果存在,则无论异常发生否,finally部分的语句均要执行,即便是try或catch块中含有退出方法的语句return,也不能阻止finally代码块的执行,在进行方法返回前要先执行finally块,除非执行中遇到System.exit(0)将停止程序运行,这种情形不会执行finally块。

多异常处理

多异常处理是通过在一个try块后面定义若干个catch块来实现的,每个catch块用来接收和处理一种特定的异常对象,每个catch块有一个异常类名作为参数,一个异常对象能否被一个catch语句块所接受,主要看该异常对象与catch块的异常参数的匹配情况。

[例9-2]:根据命令行输入的元素位置值查找数组元素的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Ex9_2{
public static void main(String[] args) {
int arr[]={100,200,300,400,500,600};
try{
int index1 = Integer.parseInt(args[0]);
System.out.println("元素值为:"+arr[index1]);
}catch (ArrayIndexOutOfBoundsException a){
System.out.println("数组下标出界");
}catch (NumberFormatException n){
System.out.println("请输入一个整数");
}finally {
System.out.println("运行结束");
}
}
}
  • 程序运行时,要从命令行输入一个参数,这时根据用户的输入存在各种情况。
    • 如果输入的数值是0~5之间的整数,将输出显示相应数组元素的值。
    • 如果输入的数据不是整数,则在执行Integer.parseInt(args[0]);时产生NumberFormatException异常,程序中捕获到该异常后,提示用户"请输入一个整数"
    • 两种情形将出现ArrayIndexOutOfBoundsException异常,一种是用户未输入命令行参数,另一种是用户输入序号超出数组范围,程序中捕获到该类异常后,显示"数组下标出界"
    • 无论异常是否发生,程序最后要执行finally块的内容。
  • 同一个try对应有多个catch块,还要注意catch的排列次序,以下排列将不能通过编译,原因在于Exception是ArithmeticException的父类,父类包含子类范畴,如果发生算术异常,第一个catch块中已经可以捕获,所以第二个catch将无意义。
1
2
3
4
5
6
7
8
try{
int x =4/0;
System.out.println("come here?");
}catch(Exception e){
System.out.println("异常!"+e.toString());
}catch (ArithmeticException e){
System.out.println("算术运行异常! "+e.toString());
}
  • 如果将两个catch块颠倒,则编译就可以通过,但运行发生ArithmeticException异常时,遇到一个成功匹配的catch块,以后的catch块不再进行匹配检查。
  • 在某些应用中,编程人员也可以根据程序的特殊逻辑在用户程序里自己创建自定义的异常类和异常对象,主要用来处理用户程序中特定的逻辑运行错误。

finall 与 try 中 return 的执行顺序

  • try语句在返回前,将其他所有的操作执行完,保留好要返回的值,而后转入执行finally中的语句,而后分为以下三种情况:
    1. 如果finally中有return语句,则会将try中的return语句”覆盖"掉,直接执行finally中的return语句,得到返回值,这样便无法得到try之前保留好的返回值。
    2. 如果finally中没有return语句,也没有改变要返回值,则执行完finally中的语句后,会接着执行try中的return语句,返回之前保留的值。
    3. 如果finally中没有return语句,但是改变了要返回的值,这里有点类似与引用传递和值传递的区别,分以下两种情况,:
      1. 如果return的数据是基本数据类型或文本字符串,则在finally中对该基本数据的改变不起作用,try中的return语句依然会返回进入finally块之前保留的值。
      2. 如果return的数据是引用数据类型,而在finally中对该引用数据类型的属性值的改变起作用,try中的return语句返回的就是在finally中改变后的该属性的值。

自定义异常类设计

创建用户自定义异常一般是通过继承Exception类来实现的,在自定义异常类中一般包括异常标识,构造方法和toString()方法。

[例9-3]:一个简单的自定义异常类。

1
2
3
4
5
6
7
8
9
10
11
public class MyException extends Exception{
String id;// 异常标识。

public MyException(String str){
id = str;
}

public String toString(){
return ("异常:"+id);
}
}

说明:构造方法的作用是给异常标识赋值,toString()方法在需要输出异常的描述时使用,在已定义异常类的基础上也可以通过继承编写新类型类。

抛出异常

前面看到的异常例子均是系统定义的异常,所有系统定义的运行异常都可以由系统在允许程序过程中自动抛出,而用户设计的异常,则要在程序中通过throw语句抛出,异常本质上是对象,因此throw关键词后面跟的是new运算符来创建一个异常对象。

1
2
3
4
5
6
7
8
9
public class TestException{
public static void main(String[] args) {
try {
throw new MyException("一个测试异常");
} catch (MyException e) {
System.out.println(e);
}
}
}

说明:在try语句块中通过throw语句抛出创建的异常对象,在catch块中将捕获的异常输出。

方法的异常声明

如果某一个方法中有异常抛出,有两种选择:一是在方法内对异常进行捕获处理,二是在方法中不处理异常,将异常处理交给外部调用程序,通常在方法头使用throws子句列出该方法可能产生哪些异常,例如以下main将获取一个输入字符并显示。

1
2
3
4
try{
char c = (char)System.in.read();
System.out.println("你输入的字符是: "+c);
}catch (java.io.IOException e) { }

对于IO异常,如果在该方法中省去异常处理,则编译时将检测到未处理IO异常而提示错误,但如果在main方法头加上throws子句则是允许的,例如:

1
2
3
4
public static void main(String[] args) throws java.io.IOException {
char c = (char)System.in.read();
System.out.println("你输入的字符是: "+c);
}

初学者要注意,throw语句和throws子句的差异性,一个是抛出异常,另一个是声明方法将产生某个异常,在一个实际方法中它们的位置如下:

1
2
3
4
5
修饰符返回类型方法名(参数列表) throws 异常类名列表{
...
throws
...
}

[例9-4]:设计一个方法计算一元二次方程的根,并测试方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Find_root {
static double[] root(double a, double b, double c)
throws IllegalArgumentException {
double x[] = new double[2];
if (a == 0) {
throw new IllegalArgumentException("a 不能为零");
} else {
double disc = b * b - 4 * a * c;
if (disc < 0)
throw new IllegalArgumentException("b*b-4ac<=0");
x[0] = (-b + Math.sqrt(disc)) / (2 * a);
x[1] = (-b - Math.sqrt(disc)) / (2 * a);
return x;
}
}

public static void main(String[] args) {
try {
double x[] = root(2.0, 5, 3);
System.out.println("方程根为:" + x[0] + "," + x[1]);
} catch (Exception e) {
System.out.println(e);
}
}
}

方程根为:-1.0,-1.5

说明:本例抛出异常利用了系统的一个异常类,IllegalArgumentException,方法声明了异常并不代表该方法肯定产生异常,也就是说,异常的发生是有条件的,不妨修改程序,将root调用的第一个参数改为0,在编译运行程序,则结果为:

1
java.lang.IllegalArgumentException:a不能为零。

注意:在编写类继承代码时,子类在覆盖父类带throws子句的方法时,子类的方法声明的throws子句抛出的异常不能超出父类方法的异常范围,换句话说,子类方法抛出的异常可以是父类方法中抛出异常的子类,子类方法也可以不抛出异常,如果父类方法没有异常声明,则子类的覆盖方法也不能出现异常声明。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!