Java 的异常机制主要依赖于try 、catch 、finally 、throw 和 throws 五个关键字,其中 try 关键字后紧跟一个花括号括起来的代码块(花括号不能省略),简称 try 块,它里面放置可能引发异常代码。 catch 后对应异常类型和一个代码块,用于表明该 catch 块用于处理这种类型代码块 。 多个 catch 块后还可以跟一个 finally 块,finally块用于回收在 try 块里打开的物理资源,异常机制会保证 finally 块总会被执行。 throws 关键字主要在方法签名中使用,用于声明该方法可能抛出的异常;而 throw 用于抛出一个实际的异常, throw 可以单独作为语句使用,抛出一个具体异常对象。
演示程序中异常:
public static void main(String[] args) { Scanner input =new Scanner(System.in); System.out.println("请输入被除数:"); int num1=input.nextInt(); System.out.println("请输入被除数:"); int num2= input.nextInt(); System.out.println(String.format("%d /%d =%d", num1,num2,num1/num2)); System.out.println("感谢使用本程序!"); }
在正常情况下,用户会按照系统的提示输入整数,除数不为 0 ,运行结果如下:
但是用户没按照要求进行输入,如被除数输入了“B”,则程序运行时将会发生异常,运行如下:
或者输入 0,则程序运行时也将发生异常,运行如图:
从结果我们可以看出一旦出现异常,程序将会立刻结束,不仅计算机和输出商的语句不被执行,而且输出“感谢使用本程序!” 的语句也不执行。那么我们该如何解决异常呢?如果我们用 if ——else语句来对各种异常情况进行来处理。我们会发现代码加入了大量的异常情况判断和处理代码,把很大一部分时间放在了处理异常代码上,放在了“堵漏洞”上,减少了编写业务代码的时间,必然影响开发效率。
那么同样一段代码,把可能出现异常的代码放入 try 语句块中,并使用 catch 语句块捕获异常。
public static void main(String[] args) { Scanner input =new Scanner(System.in); System.out.println("请输入被除数:"); int num1=input.nextInt(); try { System.out.println("请输入被除数:"); int num2= input.nextInt(); System.out.println(String.format("%d /%d =%d", num1,num2,num1/num2)); System.out.println("感谢使用本程序!"); } catch (Exception e) { System.out.println("出现错误:被除数和除数必须是整数,"+"除数不能为0"); e.printStackTrace(); } }
再次输入以上的三种情况,
第一种:
第二种:
第三种:
如果希望无论是否发生异常,都执行输出“感谢使用蹦本程序!” 的语句,,那么只需要在 try——catch 语句块后加 finally 块,把该语句放入finally 块,无论发生异常,finally 开中的代码总能被执行。
public static void main(String[] args) { Scanner input =new Scanner(System.in); System.out.println("请输入被除数:"); int num1=input.nextInt(); try { System.out.println("请输入被除数:"); int num2= input.nextInt(); System.out.println(String.format("%d /%d =%d", num1,num2,num1/num2)); } catch (Exception e) { System.out.println("出现错误:被除数和除数必须是整数,"+"除数不能为0"); e.printStackTrace(); } finally{ System.out.println("感谢使用本程序!"); } }
结果:
即使在 try 块和 catch 块中存在 return 语句,finally 块中语句也会被执行。
public static void main(String[] args) { Scanner input =new Scanner(System.in); System.out.println("请输入被除数:"); int num1=input.nextInt(); try { System.out.println("请输入被除数:"); int num2= input.nextInt(); System.out.println(String.format("%d /%d =%d", num1,num2,num1/num2)); return; //finally语句块仍旧会被执行 } catch (Exception e) { System.out.println("出现错误:被除数和除数必须是整数,"+"除数不能为0"); return; //finally语句块仍旧会被执行 } finally{ System.out.println("感谢使用本程序!"); } }
执行顺序:执行 try 块或 catch 中之前的语句,执行 finally 块中的语句,执行 try 块或 catch 中的 return 语句退出。
运行结果:
finally 块中语句不被执行的唯一情况:在异常处理代码中执行System.exit(1),将退出Java虚拟机。
public static void main(String[] args) { Scanner input =new Scanner(System.in); System.out.println("请输入被除数:"); int num1=input.nextInt(); try { System.out.println("请输入被除数:"); int num2= input.nextInt(); System.out.println(String.format("%d /%d =%d", num1,num2,num1/num2)); } catch (Exception e) { System.out.println("出现错误:被除数和除数必须是整数,"+"除数不能为0"); System.exit(1); } finally{ System.out.println("感谢使用本程序!"); } }
运行结果:
》多重catch块
一段代码可能会引发多种类型异常,这时,可以在一个 try 语句块后面跟多个 catch 语句块,分别处理不同的异常。但排列顺序必须是从子类到父类,最后一个一般都是Execption 类,因为所有异常子类都继承自 Exception类,所以如果将父类异常放到前面,那么所有的异常都将被捕获,后面 catch 块中的子类异常讲的不到被执行的机会。当运行时,系统冲上到下分别对每个 catch 语句块处理的异常的异常类型进行检测,并执行第一个与异常类型匹配的 catch语句。执行其中的一条 catch 语句之后,其后的 catch 语句都将被忽略。
public static void main(String[] args) { Scanner input =new Scanner(System.in); System.out.println("请输入被除数:"); int num1=input.nextInt(); try { System.out.println("请输入被除数:"); int num2= input.nextInt(); System.out.println(String.format("%d /%d =%d", num1,num2,num1/num2)); } catch (InputMismatchException e) { System.err.println("被除数和除数必须是整数。"); }catch (ArithmeticException e) { System.err.println("除数不能为零。"); } catch (Exception e) { System.err.println("其它未知。"); }finally{ System.out.println("感谢使用本程序!"); } }
运行结果:
》声明异常——throws
如果一个方法体中抛出异常,我们就希望调用者能及时地捕获异常,那么如何通知调用着呢?Java 语言中通过关键字 throws 声明某个方法可能抛出的各种异常,throws 可以抛出多个异常,之间用逗号隔开。
public static void main(String[] args) { try { divide(); } catch (Exception e) { System.err.println("出现错误:被除数和除数必须是整数" + "除数不能为零"); } finally { System.out.println("感谢使用本程序!"); } } public static void divide() throws Exception { Scanner input = new Scanner(System.in); System.out.println("请输入被除数:"); int num1 = input.nextInt(); System.out.println("请输入被除数:"); int num2 = input.nextInt(); System.out.println(String.format("%d /%d =%d", num1, num2, num1 / num2)); }
从上面看出 divide() 方法使用 throws 声明抛出了异常,并在 main 方法里面调用了此方法,通过 try —— catch 捕获了异常。如果不通过 try ——catch 处理异常,我们可以继续使用 throws 抛出异常
public static void main(String[] args) throws Exception { divide(); } public static void divide() throws Exception { Scanner input = new Scanner(System.in); System.out.println("请输入被除数:"); int num1 = input.nextInt(); System.out.println("请输入被除数:"); int num2 = input.nextInt(); System.out.println(String.format("%d /%d =%d", num1, num2, num1 / num2)); }
但是使用这种方法需要注意:调用者抛出的异常一定要比被调用抛出的异常更大或等于。
》抛出异常——throw
除了系统自动抛出异常,在编程过程中,我们往往遇到这样的情形:有些问题是系统无法自动发现并解决的,如年龄不在正长范围内、性别输入不是 “男” 或 “女” 等,此时需要程序员而不是系统来自动抛出异常,把问题提交给调用者去解决。
》使用 throw 在方法内抛出异常
public class Person { private String sex; public String getSex() { return sex; } public void setSex(String sex) throws Exception { if ("男".equals(sex) || "女".equals(sex)) { this.sex = sex; } else { throw new Exception("性别必须是\"男\"或者\"女\"!"); } } }
public static void main(String[] args) throws Exception { Person person = new Person(); person.setSex("hao"); }
输出结果:
throw 和 throws 的区别表现在以下三个方面。
》作用不同 : throw 用于在程序中抛出异常;throws 用于声明在该方法内抛出了异常。
》使用位置不同:throw 位于方法体内部,可以作为单独语句使用;throws 必须跟在方法参数列表的后面,不能单独使用。
》内容不同:throw 抛出一个异常对象,而且只能是一个;throws 后面跟异常类,而且可以跟多个以异常类。
》异常的分类
Throwable 类 :所有异常类型都是 Throwable 类的子类,它派生两个子类,即Error 和 Exception 。
Error类 :表示紧靠程序本身无法恢复的严重错误,如内存溢出动态链接失败、虚拟机错误。应用程序不应该抛出这种类型的对象(一般是由虚拟机抛出)。假如出现这种错误,除了尽力使程序安全退出外,在其他方面是无能为力的。所以在进行程序设计时,应该更关注Exception类。
Exception 类:由Java 应用程序抛出和处理的严重错误,如需文件找不到、网络连接不通或中断。算术运算出错(如被零除)、数组下标越界、装载了一个不存在的类、对 null 对象的操作、类型转换异常类等。他的各种不同的子类分别对应不同类型的异常。
运行时异常:包括RuntimeException 及其所有子类,不要求程序必须对它们做出处理。
Checked 异常(非运行时异常):除了运行时异常外的其他由Exception 继承来的异常类。程序必须捕获或者声明抛出这种异常,否则会出现编译错误,无法通过编译。处理方式包括来钟:通过 try——catch 在当前位置捕获并处理异常;通过throws 声明抛出异常,交给上一级调用方法处理。
》自定义异常
当 JDK 中的异常类型不能满足出现的需要时,可以自定义异常类。
public class GenderException extends Exception { public GenderException() { super(); } public GenderException(String message, Throwable cause) { super(message, cause); } public GenderException(String message) { super(message); } public GenderException(Throwable cause) { super(cause); } }
public class Person { private String sex; public String getSex() { return sex; } public void setSex(String sex) throws Exception { if ("男".equals(sex) || "女".equals(sex)) { this.sex = sex; } else { throw new GenderException("性别必须是\"男\"或者\"女\"!"); } } }