异常(什么是异常、异常的体系结构、优雅的异常处理、异常信息的分析与程序调试、自定义异常)_Wei_Hss的博客-程序员宅基地

技术标签: jvm  java  servlet  java基础1  

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

比如你使用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() 打印异常的堆栈信息

堆栈信息查看:

 

 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/H215919719/article/details/126203464

智能推荐

三种IP传输层协议TCP、UDP、SCTP与通信系统四面_sctp和arp_so~what的博客-程序员宅基地

IP传输层 - 三种IP传输层协议TCP、UDP、SCTP与通信系统四面:管理面、业务数据面、同步面、信令面_sctp和arp

移动平台基础开发(二)_java调用dimens.xml-程序员宅基地

移动平台基础开发(二)1 res资源文件夹1 res资源文件夹_java调用dimens.xml

C++封装继承多态-程序员宅基地

封装也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。 把对象的属性和服务结合成一个独立的系统单元。 尽可能隐蔽对象的内部细节。对外形成一个边界(或者说一道屏障),只保留有限的对外接口使之与外部发生联系。继承继承对于软件复用有着重要意义,是面向对象技术能够提高软件开发效率的重要原因之一。 定义:特殊类的对象拥有..._c++封装继承多态

java发送outlook邮件_通过Java代码发送OutLook邮件_破天学长的博客-程序员宅基地

准备我们想通过Java代码实现发送OutLook邮件,必须准备以下材料:OutLook邮箱目标邮箱查看OutLook邮箱信息打开OutLook邮箱,在Settings中搜索或找到SMTP:打开以下界面,拿到我们想要的数据(ServerName 以及 Port),如图:JAVA项目使用Maven或者创建一个普通项目,选择导入Maven依赖或导入jar包,我这里使用的是Maven创建的Java项目,所..._java调用outlook发邮件

LevelDB 源码分析_leveldb源码分析-程序员宅基地

本文基于leveldb 1.9.0代码。整体架构如上图,leveldb的数据存储在内存以及磁盘上,其中:memtable:存储在内存中的数据,使用skiplist实现。 immutable memtable:与memtable一样,只不过这个memtable不能再进行修改,会将其中的数据落盘到level 0的sstable中。 多层sstable:leveldb使用多个层次来存储sstable文件,这些文件分布在磁盘上,这些文件都是根据键值有序排列的,其中0级的sstable的键值可能会_leveldb源码分析

GHOSTXPSP3电脑爱好者V9.9装机版-程序员宅基地

使用IT天空论坛最新封装工具制作全自动无人值守安装,采用万能GHOST技术,安装系统过程只需5-8分钟,适合新旧各种机型。集成DX9.0c最新版集成常用VB/VC++2005、2008、2010、2012、2013/运行库、最新万能驱动助理稳定版安装过程会自动删除各分区下可能存在的AUTORUN病毒TcpIP 连接数修改为 1000;破解Uxthe...

随便推点

poj1751-Java-Prim带打印路径_poj1751 java-程序员宅基地

这个题走了点坑,我先用Prim写这个, 但是最后打印路径的时候,就没想到在贪心边的时候怎么找到这个边的起点,于是觉得无法打印出端点,就觉得应该上Kruskal, kruskal打印边多舒服啊。但是事实是, n=750的数据,加边的话是 n*n/2 的数据量,内存限制又是10000k ,所以果然MLE了。回头想想用prim的话,其实只用再维护一个from[]数组,在更新当前最短距离的时候顺便更新..._poj1751 java

一分钟带你彻底理解同步异步,阻塞非阻塞的区别_open非阻塞和阻塞式的区别-程序员宅基地

同步异步&阻塞非阻塞区别1. 同步&异步2. 阻塞&非阻塞. 几种IO分类3.1. 同步阻塞IO3.2. 同步非阻塞IO3.3. IO多路复用(Reactor模式)3.4. 异步IO(Proactor模式)1. 同步&异步同步与异步:关心的是消息通知机制如果调用方发起调用后,需要等待这个返回值,调用方主动去等它的返回,那么就是同步如果调用方发起调用后,立刻返回了,不同调用方去主动等它返回或者主动询问它返回,那么就是异步下面简单列举几个例子同步A a = read_open非阻塞和阻塞式的区别

完美解决IE9浏览器出现的对象未定义问题_websocket 未定义-程序员宅基地

目前Window7的机器上,使用IE9浏览器的用户很多,但是IE9在兼容性上做了较严格的控制,导致很多程序在chrome,firefox,ie6,ie7,ie8上可以正常运行,在ie9上确出现了各种问题,这里要说的其中一个问题,就是对象为定义,特别是单一个页面上嵌套了多层iframe/frame的时候,往往会出现:Array对象未定义$对象未定义jQuery对象未定义Json对象未定义unde_websocket 未定义

使用微信临时素材库上传与获取图片_从微信拖图片到文件夹的标识-程序员宅基地

在开发微信小程序的客服,发送客服消息时,由于使用到了图片,于是用到了微信的临时素材库上传图片调用以下接口:POST https://api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&type=TYPE请求参数属性类型默认值必填说明access_tokenstring..._从微信拖图片到文件夹的标识

CentOS5.4 安装过程(图解)_centos 5.4安装-程序员宅基地

CentOS5.4 安装过程(图解) 一:虚拟机配置:网络连接选择桥接模式 二:开始安装CentOS5.4进入CentOS安装界面,直接回车。注意:如果你实际机器的内存是512或者是更低,将会提示你内存不足以支持图形界面安装,一般玩技术最好是安装2G内存或者更高 三:输入回车键以后将进入光驱检查界面:如果你存在光驱的话依旧选择OK,如果_centos 5.4安装

一招教你如何杀死被占用的端口号(解决8080端口号被占用!)_怎么把端口号8040杀死-程序员宅基地

一、查询监听的端口号:netstat -ano | findstr 8080二、根据监听的端口号终止进程 (这里的是13336)taskkill -PID 13336 -Fok_怎么把端口号8040杀死