异常(什么是异常、异常的体系结构、优雅的异常处理、异常信息的分析与程序调试、自定义异常)

(一) 什么是异常(程序没有语法错误, 可能产生的运行时错误)

比如你使用java程序开发了一个计算器,可以让用户进行计算,但是在计算除法的过程中(程序运行过程中),用户把除数设为0, 这时我们的程序执行就会出错(大家都读过小学,知道除法中,除数不能为0),即抛出异常。异常情况是指程序在运行时,可能由与外部系统的条件变更(与我们一厢情愿所设想的不一设)而导致程序可能会出错的情况,如我们的代码要连结数据库,但数据库未启动,要创建目录,操作系统上却己存在同名的真实文件;即所谓异常是指可能(仅是可能)由与外部系统的,导致程序可能出错(中断运行)的原因。

代码如下:

import java.util.Scanner;
/**
 * 演示除数为0的情况
 * @author pactera
 *
 */
public class TestException {
    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("计算结果如下:");
        System.out.println(num1+"/"+num2+"="+(num1/num2));
    }
}
​

当我们除数不是0的时候,程序是正常执行的:

 但是当我们的除数输入的是0,程序就会出现错误,也就是我们说的异常:

 (二)异常的体系结构

java为我们提供了非常完美的异常处理机制,使得我们可以更加专心于我们的程序,在使用异常之前我们需要了解它的体系结构:

 从上面这幅图可以看出,Throwable是java语言中所有错误和异常的超类(万物即可抛)。它有两个子类:Error、Exception。

其中Error为错误,是程序无法处理的,如OutOfMemoryError、ThreadDeath等,出现这种情况你唯一能做的就是听之任之,交由JVM来处理,不过JVM在大多数情况下会选择终止线程。

Exception是程序可以处理的异常。它又分为两种CheckedException(受捡异常),一种是UncheckedException(不受检异常)。其中CheckException发生在编译阶段,必须要使用try…catch(或者throws)否则编译不通过。而UncheckedException发生在运行期,具有不确定性,主要是由于程序的逻辑问题所引起的,难以排查,我们一般都需要纵观全局才能够发现这类的异常错误,所以在程序设计中我们需要认真考虑,好好写代码,尽量处理异常,即使产生了异常,也能尽量保证程序朝着有利方向发展。

(三) 优雅的异常处理

Java具有代码级的强制性异常检测机制,即许多常见的可预料的异常可能都必须编写代码处理,否则就无法编译通过。

3.1 try-catch

网上流传这样一个笑话:世界上最真情的相依,是你在try我在catch。无论你发神马脾气,我都默默承受,静静处理。对于初学者来说处理异常就是try…catch, try…catch确实是用的最多也是最实用的。

如果一段代码可能会抛出异常---编译器检测或程序员认为会,就需要将这些代码放在try catch块中,如下代码示例;这很好理解:try指“尝试”执行可能出现异常的代码,如果成功,则乎略备用方案,即(B)区的代码;但如失败,代码会catch(捕获)到一个异常对象,放弃(A)计划,开始执行(B)计划!

try{
可能抛出异常的代码. . .
//如果没有问题出理,执行如下面的代码
(A)其它计算代码…
}catch(Exception ef){
  //如果出现异常后执行的代码:
(B)出了异常情况的计算代码. . .
}

try catch结构的异常处理提供了这样一种机制:如果代码执行成功,程序流程正常,(B)块的代码将不会执行;如果执行时(A)代码前的语句出现异常,(A)代码将不会执行,程序跳转到(B)代码块开始执行,同时,可(B)代码块中可以得到Exception类型变量ef对这个异常对象的引用,可以调用ef. printStackTrace();方法打印出异常的详细细信息;这为程序从错误中恢复提供了可行的手段。

import java.util.Scanner;
​
/**
 * 演示除数为0的情况
 * @author pactera
 *
 */
public class TestException {
    public static void main(String[] args) {
        try{//放的是可能会出现异常的代码
            Scanner input = new Scanner(System.in);
            System.out.println("请输入被除数:");
            int num1 = input.nextInt();
 System.out.println("请输入除数:");
            int num2 = input.nextInt();
            System.out.println("计算结果如下:");
            System.out.println(num1+"/"+num2+"="+(num1/num2));
            System.out.println("========程序结束!!!!=============");
        }catch(ArithmeticException e){ //catch是捕获异常,并进行处理
            //需要注意,我们这个catch捕获的只能是ArithmeticException异常或者其子类异常,对其他异常不捕获
            e.printStackTrace();//可以在控制台打印详细的异常信息,在开发阶段使用,便于调试代码
//异常的处理,我们简单输出一句话,但是在真实的项目中,不能只是输出一句话,而是要做相应的处理
            System.out.println("你丫的没读过小学吗?不知道除数不能为0吗");
        }
       System.out.println("============程序结束!!!!==================");
    }
}


 代码运行情况:

第一种情况,当程序正常执行,try中的代码执行完,catch中的代码不会执行,catch后面的代码会执行

第二种情况,当程序发生异常,并且异常的类型与catch小括号的异常一样,try中发生异常处之后的代码不会执行,则执行catch中的代码,catch后的代码也会执行.

 

第三种情况,当程序发生异常, 并且异常的类型与catch小括号的异常不一样, try中发生异常处之后的代码不会执行,也不执行catch中的代码,catch后的代码也不执行.程序会在发生异常区终止程序.

 

需要注意的是:

1.try catch块中变量做用域:try块中定义的变量符合我们前面所讲的变量做用范围的规则,即变量只能在限定自己最近的一对大括号内使用;即try catch块内一对大括号中定义的变量不能在后面的代码块内使用;

2.方法返回值:如果方法有定义的返回值,这个方法就有可以在正常执行时有一个返回值,或在catch到异常时有个返回值---不能仅仅只在try块中return一个返回值。

3.要注意的是,并不是所有的异常都强制需要try catch,在java中,异常分为强制检测和非强制检测二种:

非强制检测在编译时,不需要try catch,但如因程序员编码时逻辑错误,在运行时就会抛出,如下代码:

int[] ia=new int[10];
ia[10]=100;

这段代码在运行时,就会抛出Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 10 ,即数组越界的异常。

再如:

String s="123abc";
int sa=Integer.parseInt(s);//转为int型

运行时会抛出:

java.lang.NumberFormatException: For input string: "123abc"

因为要将s解析为int时,s中的字符必须是0~9之间的。

4.如果某段代码可以回出现多个异常,那么我们可以使用一个try后面接多个catch来进行处理,如果多个异常有继承关系,那么我们应该先写子类,再写父类
 

import java.util.InputMismatchException;
import java.util.Scanner;
​
 /**
 * 演示除数为0的情况
 * @author pactera
 *
 */
public class TestException {
    public static void main(String[] args) {
        try{//放的是可能会出现异常的代码
            Scanner input = new Scanner(System.in);
            System.out.println("请输入被除数:");
            int num1 = input.nextInt();
            System.out.println("请输入除数:");
            int num2 = input.nextInt();
            System.out.println("计算结果如下:");
            System.out.println(num1+"/"+num2+"="+(num1/num2));
            System.out.println("======代码正常执行完==========");
        }catch(ArithmeticException e){ //catch是捕获异常,并进行处理
            //需要注意,我们这个catch捕获的只能是ArithmeticException异常或者其子类异常,对其他异常不捕获
            e.printStackTrace();//可以在控制台打印详细的异常信息,在开发阶段使用,便于调试代码
            //异常的处理,我们简单输出一句话,但是在真实的项目中,不能只是输出一句话,而是要做相应的处理
            System.out.println("你丫的没读过小学吗?不知道除数不能为0吗");
        }catch(InputMismatchException e){
            System.out.println("你丫不知道什么是数字吗,乱输个啥");
        }catch(Exception e){ //Exception是所有异常类的父类,所以要写在最后
            System.out.println("哎,我也不知道怎么办了");
        }
        System.out.println("===========程序结束!!!!================");
    }
}

3.2 try-catch-finally

对于上面的代码,我们再来做一点改进,我想让上面的代码的”程序结束”这句话不管是否发生异常,而且不管是否catch到相同的异常,我都要输出,这是很有必要的,就比如说你开车,如果正常到达,你是不是也要熄火啊,当如果你车抛锚了,也就是出现异常了,那你是不是也要熄火,因为不熄火,就浪费资源了.同样在java中有一些东西也要必须关闭,比如后面需要学到的IO流,数据库连接,这些都是计算机的资源,所以不管是在程序是否正常执行,都必须关闭.

那这些必须关闭的资源应该放在那里呢?放在try中,不行吧,如果出现异常,那try中的代码就不会执行了,如果放在catch中,那如果ctach捕获的异常不一样,那也不会执行,所以java就给我们准备了这样一个东西,这就是finally块,finally块表示程序不管是否发生异常,总会执行.现在我们把上面的代码进行优化:
 

import java.util.InputMismatchException;
import java.util.Scanner;
​
/**
 * 演示除数为0的情况
 *
 * @author pactera
 *
 */
public class TestException {
    public static void main(String[] args) {
        try {// 放的是可能会出现异常的代码
            Scanner input = new Scanner(System.in);
            System.out.println("请输入被除数:");
            int num1 = input.nextInt();
            System.out.println("请输入除数:");
            int num2 = input.nextInt();
            System.out.println("计算结果如下:");
            System.out.println(num1 + "/" + num2 + "=" + (num1 / num2));
            System.out.println("======代码正常执行完==========");
            } catch (ArithmeticException e) { // catch是捕获异常,并进行处理
            // 需要注意,我们这个catch捕获的只能是ArithmeticException异常或者其子类异常,对其他异常不捕获
            e.printStackTrace();// 可以在控制台打印详细的异常信息,在开发阶段使用,便于调试代码
            // 异常的处理,我们简单输出一句话,但是在真实的项目中,不能只是输出一句话,而是要做相应的处理
            System.out.println("你丫的没读过小学吗?不知道除数不能为0吗");
        } catch (InputMismatchException e) {
            System.out.println("你丫不知道什么是数字吗,乱输个啥");
        } catch (Exception e) { // Exception是所有异常类的父类,所以要写在最后
            System.out.println("哎,我也不知道怎么办了");
        } finally {//不管是否异常都会执行
            System.out.println("================程序结束!!!!=====================");
        }
    }
}


注意:

1.try 代码段包含的是可能产生异常的代码

2.try 代码段后跟一个或多个catch代码段。(或跟一个finally代码段)

3.在jdk1.7之前每个catch代码段只声明一种其能处理的特定类型的异常,并提供处理的方法。 Jdk1.7之后,一个catch代码可以可以声明多个能处理的特定异常的类型,多个类型之间用”|”隔开

例如:

catch(ExceptionName1 e | ExceptionName1 e){
...... //异常的处理代码
}

4.当异常发生时,程序会中止当前的流程去执行相应的catch代码段。

5.写catch代码时,先捕获的异常的范围不能大于后捕获的异常的范围。

6.finally段的代码无论是否发生异常都执行。

3.3 throw 与throws

通过学习try-catch-finally,我们知道自己来处理异常,但是有一些异常,在我们编写的这个方法中,不知道怎么处理,那怎么办呢?

大家换个思维想一想,我处理不了的异常,那就把这个异常抛给别人(也就是调用我这个方法)的来处理,这也有点想我们所说的推卸责任.那怎么抛出异常呢? 就使用我们的throw 来抛出异常,但是你抛出了异常,是不是要告诉调用者,说我这个方法可能会抛出那些异常呢?我们推卸了责任,那也要让背黑锅的人知道他背的是什么黑锅,不能做得太绝,是吧.那怎么在方法上声明可能抛出的异常呢,就使用throws.

throw表示抛出异常,语法是:

 throw new 异常类型([异常信息]);

比如说: throw new Exception(“抛个异常玩玩”);

throws表示用来声明方法可能会抛出那些异常: 语法是:

throws 异常类型1,异常类型2…

代码演示:

//定义一个除法的方法,并声明异常
    public int division(int num1 ,int num2) throws ArithmeticException{
        if(num2 == 0){
            //抛出异常,如果抛出异常,则抛出异常后面的代码不会执行
            throw new ArithmeticException("除数不能为0");
        }
        return num1/num2;
    }

细节:

1.如果throw 异常对象; 如果异常对象类型是编译时异常(非RuntimeException异常), 一定在方法上使用throws 声明

2.如果throw 异常对象, 如果异常对象类型是运算时异常(RuntimeException异常或者子类), 方法上可以使用throws声明, 也可以不声明

3.如果方法调用其他代码(方法),其他代码,给我们抛了一个异常,

第一种方式: 使用try-catch处理异常

第二种方式: 在方法上使用throws 异常类型, 把这个异常继续往上抛, 异常可以一直往上抛, 都不处理, 只能交给jvm处理, jvm处理方式: 中断代码执行, 直接在控制台输出异常堆栈信息

(四)异常信息的分析与程序调试:

事实上,一个软件与其说是编写出来,不如说是调试出来的;我们通常大量的使用System.out.println…插入在程序中,以输出当时变量值或其它信息,供我们判断程序的执行情况,这是一种最常用的方法。

当程序出现异常进,通过调用Exception对象的printStackTrace()方法可以获取更详尽的出错信息:由上可以看出,这个异常是在我们的FileTest类的testCreateAndDelete方法的第52行代码抛出的, 即”temFile.createNewFile();”这行代码,只要根据方法的调用向上回溯,并根据异常描述信息,我们很会就会发现引起文件不能创建的原因。因此,异常确实是程序员的好朋友,向你坦白报告错误详情;提高编程能力的一个很重要的方法就是仔细观察异常信息详情

异常信息会沿着方法调用的路径向回传递,直到我们程序的入口点main方法,像上面打印出的异常信息输出的层级结构;异常向上传递如下图示:

 

 

 

(五)自定义异常

5.1 创建异常类

创建自定义异常,需要继承Exception 或其子类。习惯上包装一下父类的构造方法。

public class MyException extends Exception {
    public MyException() {
        super();
    }
    public MyException(String msg) {
        super(msg);
    }
    public MyException(Throwable cause) {
        super(cause);
    }
    public MyException(String msg, Throwable cause) {
        super(msg, cause);
    }
}

5.2 使用自定义异常类

根据我们业务需要,对某些情况, 也希望有对应的异常类, 但是jdk提供异常没有这种类型,

我们可以自己定义一个异常类,

步骤:

1.编写一个类, 规范: 异常类类名: xxxException

2.类继承java.lang.Exception或者子类

3.根据父类的构造方法生成本类的构造方法

 

  1. 在业务代码中,使用throw new 自定义异常类()
public void fun() throws MyException {

      if(true){

          throw new MyException("测试一下...");

      }

  }



public String[] createArray(int length) throws MyException {
    if (length < 0) {
        throw new MyException("数组长度小于0,不合法");
    }
    return new String[length];
}

5.3异常常用的方法

getMessage() 获取异常的错误信息

printStackTrace() 打印异常的堆栈信息

堆栈信息查看:

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值