异常
一、异常的概念
生活中的异常
正常情况下,小李每天开车去上班,耗时大约半个小时。
但是,异常情况迟早要发生!
程序中的异常
以下程序运行时会出现错误吗?
public class TestException {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.print("请输入被除数:");
int num1 = in.nextInt();
System.out.print("请输入除数:");
int num2 = in.nextInt();
System.out.println(String.format("%d / %d = %d",num1, num2, num1 / num2));
System.out.println("感谢使用本程序!");
}
}
上述代码会出现一系列的异常情况,比如输入的除数是0等。
为了解决此类问题我们尝试通过if-else来解决异常问题。
public class Test {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.print("请输入被除数:");
int num1 = in.nextInt();
System.out.print("请输入除数:");
int num2 = 0;
if (in.hasNextInt()) {// 如果输入的除数是整数
num2 = in.nextInt();
if (0 == num2) {// 如果输入的除数是0
System.err.println("输入的除数是0,程序退出");
System.exit(1);
}
} else {// 如果输入的除数不是整数
System.err.println("输入的除数不是整数,程序退出。");
System.exit(1);
}
System.out.println(String.format("%d / %d = %d",num1, num2, num1 / num2));
System.out.println("感谢使用本程序!");
}
}
但是上述使用if-else的方式有弊端:
1、代码臃肿。
2、程序员要花很大精力“堵漏洞”。
3、程序员很难堵住所有”漏洞“。
这是我们就需要使用异常机制。
什么是异常
概念:异常是指程序在运行过程中所发生的不正常的事件,它会中断正在运行的程序;通俗来说就是在程序运行过程中,出现的不正常情况叫做异常。
生活中面对异常通常会这样处理
生活中,根据不同的异常进行相应的处理,而不会就此中断我们的生活。
二、异常处理
什么是异常处理
Java编程语言使用异常处理机制为程序提供了错误处理的能力。
Java中如何进行异常处理
- Java中异常处理是通过5个关键字来实现的:try、catch、finally、throw、throws。
try-catch块
情况一:正常
示例代码:
public void method() {
try {
// 代码段(此处不会产生异常)
} catch (异常类型 ex) {
// 对异常进行处理的代码段
}
// 代码段
}
情况二:出现异常
示例代码:
public void method() {
try {
// 代码段1
// 产生异常的代码段2
// 代码段3
} catch (异常类型 ex) {
// 对异常进行处理的代码段4
}
// 代码段5
}
- printStackTrace的堆栈跟踪功能显示出程序运行到当前类的执行流程。
情况三:异常类型不匹配
示例代码:
public void method() {
try {
// 代码段1
// 产生异常的代码段2
// 代码段3
} catch (异常类型 ex) {
// 对异常进行处理的代码段4
}
// 代码段5
}
调用方法输出异常信息
方法名 | 说明 |
---|---|
void printStackTrace() | 输出异常的堆栈信息 |
String getMessage | 返回异常信息描述字符串,是printStackTrace()输出信息的一部分 |
常见的异常类型
异常类型 | 说明 |
---|---|
Exception | 异常层次结构的父类 |
ArithmeticException | 算术错误情形,如以零作除数 |
ArrayIndexOutBoundsException | 数组下标越界 |
NullPointerException | 尝试访问null对象成员 |
ClassNotFoundException | 不能加载所需的类 |
IIIegalArgumentException | 方法接收到非法参数 |
ClassCastException | 对象强制类型转换出错 |
NumberFormatException | 数字格式转换异常,如把“abc”转换成数字 |
try-catch-finally
- 在try-catch块后加入finally块
1、是否发生异常都执行。
2、不执行的唯一情况:
- 存在return的try-catch-finally块
示例代码:
public void method() {
try {
// 代码段1
// 产生异常的代码段2
} catch (异常类型 ex) {
// 对异常进行处理的代码段3
// 执行return退出方法
}
// 代码段4
}
- 当try-catch块中存在return语句,是否执行finally块,其中包含了四种情况:
情况一(try中有return,finally中没有return):
public class TryTest{
public static void main(String[] args){
System.out.println(test());
}
private static int test(){
int num = 10;
try{
System.out.println("try");
return num += 80;
}catch(Exception e){
System.out.println("error");
}finally{
if (num > 20){
System.out.println("num>20 : " + num);
}
System.out.println("finally");
}
return num;
}
}
// 输出结果如下:
// try
// num>20 : 90
// finally
// 90
// 分析:显然“return num += 80”被拆分成了“num = num+80”和“return num”两个语句,线执行try中的“num = num+80”语句,将其保存起来,在try中的”return num“执行前,先将finally中的语句执行完,而后再将90返回。
情况二(try和finally中均有return):
public class TryTest{
public static void main(String[] args){
System.out.println(test());
}
private static int test(){
int num = 10;
try{
System.out.println("try");
return num += 80;
}catch(Exception e){
System.out.println("error");
}finally{
if (num > 20){
System.out.println("num>20 : " + num);
}
System.out.println("finally");
num = 100;
return num;
}
}
}
// 输出结果如下:
// try
// num>20 : 90
// finally
// 100
// 分析:try中的return语句同样被拆分了,finally中的return语句先于try中的return语句执行,因而try中的return被”覆盖“掉了,不再执行。
情况三(finally中改变返回值num):
public class TryTest{
public static void main(String[] args){
System.out.println(test());
}
private static int test(){
int num = 10;
try{
System.out.println("try");
return num;
}catch(Exception e){
System.out.println("error");
}finally{
if (num > 20){
System.out.println("num>20 : " + num);
}
System.out.println("finally");
num = 100;
}
return num;
}
}
// 输出结果如下:
// try
// finally
// 10
// 分析:虽然在finally中改变了返回值num,但因为finally中没有return该num的值,因此在执行完finally中的语句后,test()函数会得到try中返回的num的值,而try中的num的值依然是程序进入finally代码块前保留下来的值,因此得到的返回值为10。
情况四(将num的值包装在Num类中):
public class TryTest{
public static void main(String[] args){
System.out.println(test().num);
}
private static Num test(){
Num number = new Num();
try{
System.out.println("try");
return number;
}catch(Exception e){
System.out.println("error");
}finally{
if (number.num > 20){
System.out.println("number.num>20 : " + number.num);
}
System.out.println("finally");
number.num = 100;
}
return number;
}
}
class Num{
public int num = 10;
}
// 输出结果如下:
// try
// finally
// 100
// 从结果中可以看出,同样是在finally中改变了返回值num的值,在情况三中,并没有被try中的return返回(test()方法得到的不是100),但在这里却被try中的return语句返回了。
总结:
try语句在返回前,将其他所有的操作执行完,保留好要返回的值,而后转入执行finally中的语句,而后分为以下三种情况:
情况一:如果finally中有return语句,则会将try中的return语句”覆盖“掉,直接执行finally中的return语句,得到返回值,这样便无法得到try之前保留好的返回值。
情况二:如果finally中没有return语句,也没有改变要返回值,则执行完finally中的语句后,会接着执行try中的return语句,返回之前保留的值。
情况三:如果finally中没有return语句,但是改变了要返回的值,这里有点类似与引用传递和值传递的区别,分以下两种情况:
1)如果return的数据是基本数据类型或文本字符串,则在finally中对该基本数据的改变不起作用,try中的return语句依然会返回进入finally块之前保留的值。
2)如果return的数据是引用数据类型,而在finally中对该引用数据类型的属性值的改变起作用,try中的return语句返回的就是在finally中改变后的该属性的值。
多重catch块
- 引发多种类型的异常
1、排列catch语句的顺序:先子类后父类。
2、发生异常时按照顺序逐个匹配。
3、只执行第一个与异常类型匹配的catch语句。
示例代码:
public class TestException {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
try {
System.out.print("请输入被除数:");
int num1 = in.nextInt();
System.out.print("请输入除数:");
int num2 = in.nextInt();
System.out.println(String.format("%d / %d = %d",
num1, num2, num1 / num2));
System.out.println("前面没有出现异常");
}catch(ArithmeticException e){
System.out.println("数学异常,除数不能为0");
e.printStackTrace();
}catch(InputMismatchException e){
System.out.println("输入的参数值类型不匹配");
e.printStackTrace();
}catch(NullPointerException e){
System.out.println("空指针异常");
e.printStackTrace();
}
System.out.println("感谢使用本程序!");
}
}
优点: 可以针对每一种具体的异常做出相应的更丰富的处理。
注意: 当使用多重的catch的时候一定要注意相关异常的顺序,将子类放在最前面的catch,父类放在后面的catch。
执行过程中可能存在的情况:
1、正常执行,只执行try中的代码。
2、遇到异常情况,会处理try中异常代码之前的逻辑,后面的逻辑不会执行,最后会执行catch中的代码。
3、使用多重catch的时候,会遇到异常子类不匹配的情况,此时依然会报错,因此建议在catch的最后将所有异常的父类写上。
声明异常(throws)
在异常情况出现的时候,可以使用try。。。catch。。。finally的方式对异常进行处理,除此之外,可以将异常抛出,由外部进行处理:
1、在方法调用过程中,可以存在N多个方法之间的调用,此时假如这个方法中都包含了异常情况
那么就需要在每个方法中都进行try。。。catch,另外一种比较简单的方式,就是在方法的最外层处理调用一次即可,使用throws的方法,对所有执行过程中的所有方法出现的异常进行统一集中处理。
2、如何判断是使用throws还是使用try。。。catch?
最稳妥的方式是在每个方法中都进行异常的处理;偷懒的方式是判断在整个调用的过程中,外层的调用方法是否对异常的处理,如果有,直接使用throws,如果没有, 那么就要使用try。。。catch。。。
抛出异常(throw)
三、异常的分类
四、自定义异常
何时需要自定义异常?
当JDK中的异常类型不能满足程序的需要时,可以自定义异常类。
使用自定义异常的步骤:
- 定义异常类。
- 编写构造方法,继承父类的实现。
- 实例化自定义异常对象。
- 使用throw抛出。
注意:自定义的异常类需要继承Exception或者RuntimeException。
示例代码:
public class GenderException extends Exception{
public GenderException(){
System.out.println("性别异常");
}
public GenderException(String msg){
System.out.println(msg);
}
}
五、总结
1、相同的代码在运行的时候,根据输入的参数或者操作的不同,有可能会发生异常,有可能不会发生异常,应该在写代码的过程中尽可能的保证代码的正确性,不要到处都是bug。
2、如果要解决代码中出现的异常,需要添加非常复杂的代码逻辑来进行判断,会使代码变得非常臃肿,不利于维护,可读性比较差。因此,推荐大家使用异常机制来处理程序运行过程中出现的问题。
3、程序在运行过程中如果出现了问题,会导致后面的代码无法正常执行,而使用异常机制之后可以对异常情况进行处理,同时后续的代码会继续执行,不会中断整个程序。
4、在异常的处理过程中,不要只是简单的输出错误,要尽可能的将详细的异常信息进行输出;
e.printStackTrace():打印异常的堆栈信息,可以从异常信息的最后一行开始追踪,寻找自己编写的Java类。