Java 的异常处理
文章目录
前言
有实际意义的软件本身是复杂的,软件运行的坏境也是复杂的,加之软件使用者在操作时的不确定性和不可控性,使得程序在执行过程中出现不正常,甚至导致程序运行崩溃的情况在所难免。而程序员需要尽可能控制这些”异常“情况,使软件“鲁棒”。
鲁棒性是软件质量的一个非常重要的衡量标准,也是软件工程的一个专业术语。
鲁棒是 Robust的音译,也就是健壮和强壮的意思。它是在异常和危险情况下系统生存的关键。比如说,计算机软件在输入错误、磁盘故障、网络过教或有意攻击情况下,能否不死机、不崩溃,就是该软件的鲁棒性。
——来源于百度百科
C语言系统对于“异常”,基本没有提供系统级别的处理手段,全部要靠程序员编码进行判断和处理。C++、Java这样的后起之秀提供了系统级别的异常处理手段,大大简化了对异常的处理。
对 Java 异常处理的学习和掌握,是编写出鲁棒性高的实用软件的基本功,这里必须强调:这里所说的异常,不是语法错误!凡是能在编译过程中发现的错误,都不在异常处理范围之列。
异常
什么是异常?
异常本质上是程序的错误。
在程序开发中,异常指不期而至的各种状况。它是一个事件,当发生在程序运行期间时,会干扰正常的指令流程。
异常的分类
在Java中,通过 Throwable 及其子类描述各种不同的异常类型。
Throwable有两个重要的子类:Exception 和 Error
Error
Error是程序无法处理的错误,表示运行应用程序 中较严重问题。
大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java虚拟机)出现的问题。
例如: Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。
这些错误是不可查的,因为它们在应用程序的 控制和处理能力之外,而且绝大多数是程序运行 时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。因此我们编写程序时不需要关心这类异常。
Exception
Exception 是程序本身可以处理的异常。异常处理通常只针对这种类型异常的处理。
Exception 又分为 unchecked exception(非检查异常) 和 checked exception (检查异常)。
unchecked exception(非检查异常)
unchecked exception:编译器不要求强制处置的异常。 Java编译器不会检查这些异常,在程序中可以选择捕获处理,也可以不处理,照样正常编译通过。
checked exception (检查异常,又称非运行时异常)
checked exception:编译器要求必须处置的异常,除了 RuntimeException 及其子类以外,其他都属于 Exception 类的子类。如 IOException、SQLException 等 ,Java编译器会检查这些异常,当程序中可能出现这类异常时,要求必须进行异常处理,否则编译 不会通过。
异常处理语法及过程
异常处理代码介绍
先看下面两行代码:
public class SimpleExceptionDemo {
public static void main(String[] args) {
System.out.println(12 / 0);
}
}
这里明知故犯,我们明显能够看到 Java 如何处理”除0错“。
这个程序不能够被正常执行,不过执行的结果是在 console 中出现了红色的异常提示,并清晰地说明是类 SimpleExceptionDemo 的第5行,发生了 ArithmeticException (数学异常),且是:/ by zero!
这说明,”除0错“是被 Java 系统”捕获“,并处理了,其实我们可以在我们的代码中”捕获“并处理这个异常:
package com.hb.about_exception.core;
import java.util.Scanner;
public class SimpleExceptionDemo {
public static void main(String[] args) {
int num1;
int num2;
int result;
Scanner in = new Scanner(System.in);
//从键盘输入两个相除的数
num1 = in.nextInt();
num2 = in.nextInt();
try {
result = num1 / num2;
//num2 若为 0 ,则会出现”除 0 错“异常;
//但 num2 也大概率不为 0 ;
System.out.println(num1 + "/ " + num2 + "=" + result);
} catch (ArithmeticException arithmeticException) {
//catch 意为”捕获“:这里试图捕获除 0 错异常;
System.out.println("怎么可以除 0 呢?小笨蛋");
}
in.close();
}
}
将上述程序执行两次,第一次 num2 不为 0 ,第二次 num2 为 0;
第一次执行结果如下:
7
7
7 / 7 = 1
第二次执行结果如下:
这个简单的例子至少说明关于Java异常处理的基本原则:
程序一旦发生异常,则,异常点后的语句将停止执行。
如果不存在这种异常的捕获语句,则,以后的所有代码都将停止执行,程序“崩溃”,由 JVM 接手处理异常;
如果存在,则,转而执行异常捕获语句块中的代码;并继续执行异常捕获语句块的后续代码,程序不会崩溃!
下图,对比不同情况下的程序流程:
在Java应用程序中,异常处理机制为:抛出异常 和 捕获异常;
抛出异常
- 当一个方法出现错误引发异常时,方法创建异常对象并交付运行时系统。
- 异常对象中包含了异常类型和异常出现时的程序状态等异常信息。
- 运行时系统负责寻找处置异常的代码并执行。
捕获异常
- 在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器。
- 运行时系统从发生异常的方法开始,依次回查调用栈中的方法,当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适的异常处理器。
- 当运行时系统遍历调用栈而未找到合适的异常处理器,则运行时系统终止。同时,意味着Java程序的终止。
对于运行时异常、错误或可查异常,Java技术所 要求的异常处理方式有所不同。简单地说,异常总是先被抛出,后被捕捉的。
总的来说:
异常处理通过5个关键字来实现:try、catch、 finally、throw 、throws;
try — catch — finally
-
try块: 用于捕获异常;
-
catch块: 用于处理try捕获到的异常;
-
finally块: 无论是否发生异常,都要执行的块
try 块后可接零个或者多个 catch 块,如果没有 catch 块,则必须跟一个 finally 块。
多重 catch 块
-
一旦某个catch捕获到匹配的异常类型,将进入异常处理代码。一经处理结束,就意味着整个 try - catch 语句结束。其他的catch子句不再有匹配和捕获异常类型的机会。
-
对于有多个catch子句的异常程序而言,应该尽量将捕获底层异常类的 catch 子句放在前面,同时尽量将捕获相对高层的异常类的catch子句放在后面。否则,捕获底层异常类的catch子句将可能会被屏蔽。
-
引发多种类型的异常
- 排列 catch 语句的顺序:先子类后父类;
- 发生异常时按顺序逐个匹配;
- 只执行第一个与异常类型匹配的 catch 语句;
常见的异常类型
异 常 类 型 说 明 Exception 异常层次结构的父类 ArrayIndexOutOfBoundsException 数组下标越界 NullPointerException 尝试访问 null 对象成员 ClassNotFoundException 不能加载所需的类 IllegalArgumentException 方法接收到非法参数 ClassCastException 对象强制类型转换出错 NumberFormatException 数字格式转换异常,如把"abc" 转换成数字
抛出异常和捕获异常的选择
对于异常,通常有两种处理手段:抛出和捕获。在前面的示例中,两种处理方法都使用过。上面的代码主要是讲述异常的基本语法和产生过程,对于这两种处理方式的差别并没有说明。
一般来说,如果对于某种异常,如果在本方法中有处理它的安排,那么就捕获,而且,这个异常不再向上一级方法(调用本方法的方法)抛出。这样做相当于自动响应异常,并给出有效处理。
如果本方法没有处理它的安排,那通常分两种情况:
一种情况是,这种异常不影响后续代码的逻辑和执行,那么,就直接捕获且什么都不处理(保证捕获处理块为空)即可;
另一种情况是,这种异常严重影响到后续代码的执行,甚至会影响到调用本方法的上一级方法的正常执行,那么,就把这个异常“抛出”。
还有一种简单的处理异常的原则:能处理则处理,不能处理则抛出。
当然,抛到了主函数main()那里的异常,最好还是处理一下,免得异常发生后,满屏红字。
事实上,关于异常的处理选择,要根据具体应用场景进行灵活选择,没有任何可以“套”的公式。所以,熟练掌握异常的方法只有一个:多编程。
自定义异常
用 Exception 类可以派生出“检查型异常”类;
用 RuntimeException 可以派生出“运行时异常”类。
通常建议定义“检杳型异常”,这样可以强迫程序员对异常进行捕获和处理;如果确实不需要程序员有目的的进行捕获,也可以定义“运行时异常”。
这里利用 Eclipse 帮助我们非常方便、迅速的建立一个异常类:
注意上图中框选和箭头所指处。
选择“Finish”按钮后,会出现异常类代码。这时会发现,除了代码里出现了大量构造方法外,编译器还会出现黄色警告。使用“Ctrl +1”自动修正功能,会出现下图所示的要求生成一个“序列号”的修正方案。(这里涉及到了 Java ”序列化“的问题,此处不作详细赘述)
备注:序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间,序列化是为了解决在对对象流进行读写操作时所引发的问题。
public class CoordinateOutOfRange extends Exception {
/**
*
*/
private static final long serialVersionUID = 95715270923882271L;
public CoordinateOutOfRange() {
// TODO Auto-generated constructor stub
}
public CoordinateOutOfRange(String message) {
super(message);
// TODO Auto-generated constructor stub
}
public CoordinateOutOfRange(Throwable cause) {
super(cause);
// TODO Auto-generated constructor stub
}
public CoordinateOutOfRange(String message, Throwable cause) {
super(message, cause);
// TODO Auto-generated constructor stub
}
public CoordinateOutOfRange(String message, Throwable cause, boolean enableSuppression,
boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
// TODO Auto-generated constructor stub
}
}
这样就生成一个我们自己定义的异常类了。