浅谈java异常[Exception]

一. 异常的定义

在《java编程思想》中这样定义 异常:阻止当前方法或作用域继续执行的问题。虽然java中有异常处理机制,但是要明确一点,决不应该用"正常"的态度来看待异常。绝对一点说异常就是某种意义上的错误,就是问题,它可能会导致程序失败。之所以java要提出异常处理机制,就是要告诉开发人员,你的程序出现了不正常的情况,请注意。

记得当初学习java的时候,异常总是搞不太清楚,不知道这个异常是什么意思,为什么会有这个机制?但是随着知识的积累逐渐也对异常有一点感觉了。举一个例子来说明一下异常的用途。

public class Calculator {public int devide(int num1, int num2) {//判断除数是否为0if(num2 == 0) {throw new IllegalArgumentException("除数不能为零");}return num1/num2;}
}

看一下这个类中关于除运算的方法,如果你是新手你可能会直接返回计算结果,根本不去考虑什么参数是否正确,是否合法(当然可以原谅,谁都是这样过来的)。但是我们应尽可能的考虑周全,把可能导致程序失败的"苗头"扼杀在摇篮中,所以进行参数的合法性检查就很有必要了。其中执行参数检查抛出来的那个参数非法异常,这就属于这个方法的不正常情况。正常情况下我们会正确的使用计算器,但是不排除粗心大意把除数赋值为0。如果你之前没有考虑到这种情况,并且恰巧用户数学基础不好,那么你完了。但是如果你之前考虑到了这种情况,那么很显然错误已在你的掌控之中。

二. 异常扫盲行动

今天和别人聊天时看到一个笑话:世界上最真情的相依,是你在try我在catch。无论你发神马脾气,我都默默承受,静静处理。 大多数新手对java异常的感觉就是:try...catch...。没错,这是用的最多的,也是最实用的。我的感觉就是:java异常是从"try...catch..."走来。

首先来熟悉一下java的异常体系:

Throwable 类是 Java 语言中所有错误或异常的超类(这就是一切皆可抛的东西)。它有两个子类:Error和Exception。

Error:用于指示合理的应用程序不应该试图捕获的严重问题。这种情况是很大的问题,大到你不能处理了,所以听之任之就行了,你不用管它。比如说VirtualMachineError:当 Java 虚拟机崩溃或用尽了它继续操作所需的资源时,抛出该错误。好吧,就算这个异常的存在了,那么应该何时,如何处理它呢??交给JVM吧,没有比它更专业的了。

Exception:它指出了合理的应用程序想要捕获的条件。Exception又分为两类:一种是CheckedException,一种是UncheckedException。这两种Exception的区别主要是CheckedException需要用try...catch...显示的捕获,而UncheckedException不需要捕获。通常UncheckedException又叫做RuntimeException。《effective java》指出:对于可恢复的条件使用被检查的异常(CheckedException),对于程序错误(言外之意不可恢复,大错已经酿成)使用运行时异常(RuntimeException)。

我们常见的RuntimeExcepiton有IllegalArgumentException、IllegalStateException、NullPointerException、IndexOutOfBoundsException等等。对于那些CheckedException就不胜枚举了,我们在编写程序过程中try...catch...捕捉的异常都是CheckedException。io包中的IOException及其子类,这些都是CheckedException。

三. 异常的使用

在异常的使用这一部分主要是演示代码,都是我们平常写代码的过程中会遇到的(当然只是一小部分),抛砖引玉吗!

例1. 这个例子主要通过两个方法对比来演示一下有了异常以后代码的执行流程。

public static void testException1() {int[] ints = new int[] { 1, 2, 3, 4 };System.out.println("异常出现前");try {System.out.println(ints[4]);System.out.println("我还有幸执行到吗");// 发生异常以后,后面的代码不能被执行} catch (IndexOutOfBoundsException e) {System.out.println("数组越界错误");}System.out.println("异常出现后");}/*output:异常出现前数组越界错误4异常出现后
public static void testException2() {int[] ints = new int[] { 1, 2, 3, 4 };System.out.println("异常出现前");System.out.println(ints[4]);System.out.println("我还有幸执行到吗");// 发生异常以后,他后面的代码不能被执行}

首先指出例子中的不足之处,IndexOutofBoundsException是一个非受检异常,所以不用try...catch...显示捕捉,但是我的目的是对同一个异常用不同的处理方式,看它会有什么不同的而结果(这里也就只能用它将就一下了)。异常出现时第一个方法只是跳出了try块,但是它后面的代码会照样执行的。但是第二种就不一样了直接跳出了方法,比较强硬。从第一个方法中我们看到,try...catch...是一种"事务性"的保障,它的目的是保证程序在异常的情况下运行完毕,同时它还会告知程序员程序中出错的详细信息(这种详细信息有时要依赖于程序员设计)。

例2. 重新抛出异常

public class Rethrow {public static void readFile(String file) throws FileNotFoundException {try {BufferedInputStream in = new BufferedInputStream(new FileInputStream(file));} catch (FileNotFoundException e) {e.printStackTrace();System.err.println("不知道如何处理该异常或者根本不想处理它,但是不做处理又不合适,这是重新抛出异常交给上一级处理");//重新抛出异常throw e;}}public static void printFile(String file) {try {readFile(file);} catch (FileNotFoundException e) {e.printStackTrace();}}public static void main(String[] args) {printFile("D:/file");}
}

异常的本意是好的,让我们试图修复程序,但是现实中我们修复的几率很小,我们很多时候就是用它来记录出错的信息。如果你厌倦了不停的处理异常,重新抛出异常对你来说可能是一个很好的解脱。原封不动的把这个异常抛给上一级,抛给调用这个方法的人,让他来费脑筋吧。这样看来,java异常(当然指的是受检异常)又给我们平添很多麻烦,尽管它的出发点是好的。

例3. 异常链的使用及异常丢失

定义三个异常类:ExceptionA,ExceptionB,ExceptionC

public class ExceptionA extends Exception {public ExceptionA(String str) {super();}
}public class ExceptionB extends ExceptionA {public ExceptionB(String str) {super(str);}
}public class ExceptionC extends ExceptionA {public ExceptionC(String str) {super(str);}
}

异常丢失的情况:

public class NeverCaught {static void f() throws ExceptionB{throw new ExceptionB("exception b");}static void g() throws ExceptionC {try {f();} catch (ExceptionB e) {ExceptionC c = new ExceptionC("exception a");throw c;}}public static void main(String[] args) {try {g();} catch (ExceptionC e) {e.printStackTrace();}}}
/*
exception.ExceptionC
at exception.NeverCaught.g(NeverCaught.java:12)
at exception.NeverCaught.main(NeverCaught.java:19)
*/

为什么只是打印出来了ExceptionC而没有打印出ExceptionB呢?这个还是自己分析一下吧!

上面的情况相当于少了一种异常,这在我们排错的过程中非常的不利。那我们遇到上面的情况应该怎么办呢?这就是异常链的用武之地:保存异常信息,在抛出另外一个异常的同时不丢失原来的异常。


public class NeverCaught {static void f() throws ExceptionB{throw new ExceptionB("exception b");}static void g() throws ExceptionC {try {f();} catch (ExceptionB e) {ExceptionC c = new ExceptionC("exception a");//异常连c.initCause(e);throw c;}}public static void main(String[] args) {try {g();} catch (ExceptionC e) {e.printStackTrace();}}}
/*
exception.ExceptionC
at exception.NeverCaught.g(NeverCaught.java:12)
at exception.NeverCaught.main(NeverCaught.java:21)
Caused by: exception.ExceptionB
at exception.NeverCaught.f(NeverCaught.java:5)
at exception.NeverCaught.g(NeverCaught.java:10)
... 1 more
*/

这个异常链的特性是所有异常均具备的,因为这个initCause()方法是从Throwable继承的。

例4. 清理工作

清理工作对于我们来说是必不可少的,因为如果一些消耗资源的操作,比如IO,JDBC。如果我们用完以后没有及时正确的关闭,那后果会很严重,这意味着内存泄露。异常的出现要求我们必须设计一种机制不论什么情况下,资源都能及时正确的清理。这就是finally。

public void readFile(String file) {BufferedReader reader = null;try {reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));// do some other work} catch (FileNotFoundException e) {e.printStackTrace();} finally {try {reader.close();} catch (IOException e) {e.printStackTrace();}}}

例子非常的简单,是一个读取文件的例子。这样的例子在JDBC操作中也非常的常见。(所以,我觉得对于资源的及时正确清理是一个程序员的基本素质之一。)

Try...finally结构也是保证资源正确关闭的一个手段。如果你不清楚代码执行过程中会发生什么异常情况会导致资源不能得到清理,那么你就用try对这段"可疑"代码进行包装,然后在finally中进行资源的清理。举一个例子:

public void readFile() {BufferedReader reader = null;try {reader = new BufferedReader(new InputStreamReader(new FileInputStream("file")));// do some other work//close readerreader.close();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} }

我们注意一下这个方法和上一个方法的区别,下一个人可能习惯更好一点,及早的关闭reader。但是往往事与愿违,因为在reader.close()以前异常随时可能发生,这样的代码结构不能预防任何异常的出现。因为程序会在异常出现的地方跳出,后面的代码不能执行(这在上面应经用实例证明过)。这时我们就可以用try...finally来改造:

public void readFile() {BufferedReader reader = null;try {try {reader = new BufferedReader(new InputStreamReader(new FileInputStream("file")));// do some other work// close reader} finally {reader.close();}} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}

及早的关闭资源是一种良好的行为,因为时间越长你忘记关闭的可能性越大。这样在配合上try...finally就保证万无一失了(不要嫌麻烦,java就是这么中规中矩)。

再说一种情况,假如我想在构造方法中打开一个文件或者创建一个JDBC连接,因为我们要在其他的方法中使用这个资源,所以不能在构造方法中及早的将这个资源关闭。那我们是不是就没辙了呢?答案是否定的。看一下下面的例子:

ublic class ResourceInConstructor {BufferedReader reader = null;public ResourceInConstructor() {try {reader = new BufferedReader(new InputStreamReader(new FileInputStream("")));} catch (FileNotFoundException e) {e.printStackTrace();}}public void readFile() {try {while(reader.readLine()!=null) {//do some work}} catch (IOException e) {e.printStackTrace();}}public void dispose() {try {reader.close();} catch (IOException e) {e.printStackTrace();}}
}

这一部分讲的多了一点,但是异常确实是看起来容易用起来难的东西呀,java中还是有好多的东西需要深挖的。

四. 异常的误用

对于异常的误用着实很常见,上一部分中已经列举了几个,大家仔细的看一下。下面再说两个其他的。

例1. 用一个Exception来捕捉所有的异常,颇有"一夫当关万夫莫开"的气魄。不过这也是最傻的行为。

public void readFile(String file) {BufferedReader reader = null;Connection conn = null;try {reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));// do some other workconn = DriverManager.getConnection("");//...} catch (Exception e) {e.printStackTrace();} finally {try {reader.close();conn.close();} catch (Exception e) {e.printStackTrace();}}}

从异常角度来说这样严格的程序确实是万无一失,所有的异常都能捕获。但是站在编程人员的角度,万一这个程序出错了我们该如何分辨是到底是那引起的呢,IO还是JDBC...所以,这种写法很值得当做一个反例。大家不要以为这种做法很幼稚,傻子才会做。我在公司实习时确实看见了类似的情况:只不过是人家没有用Exception而是用了Throwable。

例2. 这里就不举例子了,上面的程序都是反例。异常是程序处理意外情况的机制,当程序发生意外时,我们需要尽可能多的得到意外的信息,包括发生的位置,描述,原因等等。这些都是我们解决问题的线索。但是上面的例子都只是简单的printStackTrace()。如果我们自己写代码,就要尽可能多的对这个异常进行描述。比如说为什么会出现这个异常,什么情况下会发生这个异常。如果传入方法的参数不正确,告知什么样的参数是合法的参数,或者给出一个sample。

例3. 将try block写的简短,不要所有的东西都扔在这里,我们尽可能的分析出到底哪几行程序可能出现异常,只是对可能出现异常的代码进行try。尽量为每一个异常写一个try...catch,避免异常丢失。在IO操作中,一个IOException也具有"一夫当关万夫莫开"的气魄。

五.总结

总结非常简单,不要为了使用异常而使用异常。异常是程序设计的一部分,对它的设计也要考究点。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/434368.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot使用validation进行自参数校验

一:介绍 在 SpringBoot 项目开发中,很多与数据库交互的参数需要校验数据正确性。很多小伙伴会把参数判断写进代码里,但是这种写法往往会有低可读性以及多处使用的时候,需要变更验证规则时,不易于维护等缺点。今天给大家…

Java之多态

文章目录 1. 多态1.1 多态的概念 2. 方法的重写3. 向上转型3.13.2 发生向上转型的时机 4. 动态绑定和静态绑定5. 什么是多态5.15.2 多态的优缺点 6. 避免在构造方法中调用重写的方法7. 向下转型![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/fd1fa83140d94f37ab3b88…

HTML+CSS 水滴登录页

文章目录 一、效果演示二、Code1.HTML2.CSS 三、实现思路拆分 一、效果演示 实现了一个水滴登录页的效果。页面包含一个水滴形状的登录框和两个按钮,登录框包括用户名、密码和登录按钮,按钮分别为忘记密码和注册。整个页面的设计非常有创意,采…

一些超好用的 GitHub 插件和技巧

聊聊我平时使用 GitHub 时学到的一些插件、技巧。 ‍ ‍ 浏览器插件 在我的另一篇博客 浏览器插件推荐 里提到过跟 GitHub 相关的一些插件,这里重复下: Sourcegraph:在线打开项目,方便阅读,将 GitHub 变得和 IDE …

【AI创作组】Matlab中进行符号计算

提示:代码一定要自己运行过才算数…… 1. 符号计算工具箱介绍 1.1 工具箱功能 MATLAB的符号计算工具箱,即Symbolic Math Toolbox,是一套强大的数学软件工具,它使得MATLAB具备了符号运算的能力。该工具箱提供了一系列函数,用于求解、绘制和操作符号数学方程。用户可以直接…

【Linux】修改用户名用户家目录

0、锁定旧用户登录 如果旧用户olduser正在运行中是无法操作的,需要先禁用用户登录,然后杀掉所有此用户的进程。 1. 使用 usermod 命令禁用用户 这将锁定用户账户,使其无法登录: sudo usermod -L olduser2. 停止用户的进程 如…

Woocommerce怎么分类显示产品?如何将Shopify的产品导入到Woocommerce?

WooCommerce作为WordPress的一个电子商务插件,功能强大、使用简洁,能够轻松集成到WordPress网站中,为用户提供了一个完整的在线商店解决方案,在国外还是挺受欢迎的。 Woocommerce怎么分类显示产品? 在Woocommerce中&a…

【微服务】springboot 实现动态修改接口返回值

目录 一、前言 二、动态修改接口返回结果实现方案总结 2.1 使用反射动态修改返回结果参数 2.1.1 认识反射 2.1.2 反射的作用 2.1.3 反射相关的类 2.1.4 反射实现接口参数动态修改实现思路 2.2 使用ControllerAdvice 注解动态修改返回结果参数​​​​​​​ 2.2.1 注解…

docker pull 超时的问题如何解决

docker不能使用&#xff0c;使用之前的阿里云镜像失败。。。 搜了各种解决方法&#xff0c;感谢B站UP主 <iframe src"//player.bilibili.com/player.html?isOutsidetrue&aid113173361331402&bvidBV1KstBeEEQR&cid25942297878&p1" scrolling"…

已解决:“ModuleNotFoundError:No module named apex”

首先遇到这个问题不可以直接简单粗暴的使用&#xff1a;“pip install apex”直接安装模块来解决&#xff0c;这样的话程序还是会继续报错“ModuleNotFoundError&#xff1a;No module named apex”&#xff0c;别问我怎么知道&#xff0c;问就是深受其害&#xff01; 去网上查…

基于pdf.js实现对pdf预览、批注功能、 保存下载pdf,适配H5,平板 踩坑记录

项目场景&#xff1a; 在APP端实现对pdf的批注,能够下载保存.能够获取批注信息同时能够重新渲染到pdf中.基于pdf.js-4.5.136版本源码实现。pc端能够正常预览下载pdf&#xff0c;构建打包后嵌入uniapp的webview遇到的问题记录 问题描述 将构建打包后的代码嵌入到uniapp中&…

ELK-03-skywalking监控linux系统

文章目录 前言一、下载node_exporter二、启动node_exporter三、下载OpenTelemetry Collector四、启动OpenTelemetry Collector4.1 将配置文件下载到同级目录4.2 启动 五、查看总结 前言 skywalking安装完成后&#xff0c;开始我们的第一个监控-监控linux系统。 参考官方文档&a…

长列表加载性能优化

一、长列表优化概述 列表是应用开发中最常见的一类开发场景&#xff0c;它可以将杂乱的信息整理成有规律、易于理解和操作的形式&#xff0c;便于用户查找和获取所需要的信息。应用程序中常见的列表场景有新闻列表、购物车列表、各类排行榜等。随着信息数据的累积&#xff0c;特…

DMA的原理

一、介绍 DMA&#xff08;Direct Memory Access&#xff09;是一种允许设备直接与内存进行数据交换的技术&#xff0c;无需‌CPU干预。DMA的主要功能是提供在‌外设和存储器之间或者存储器和存储器之间的高速数据传输。比如使用ADC进行数据采集&#xff0c;可以直接将数据存入…

干货 | 2024制造业数字化现状调查白皮书(免费下载)

导读&#xff1a;在这本白皮书中&#xff0c;我们询问了制造商有关数字化转型的工作情况、2024 年的优先事项和可持续性。研究结果清楚地表明&#xff0c;在数字化方面处于领先地位的制造商转型项目比那些没有规划或刚刚起步的项目实现的价值要大得多。 加入知识星球或关注下方…

运维转型大模型:全面指南与实战总结

运维心里苦谁做谁知道&#xff0c;有时候感觉自己像一个杂工&#xff0c;在公司都快变成一个修电脑的了&#xff0c;不装了我转行了&#xff0c;给大家分享一点经验&#xff0c;希望能帮助到你们。 运维工程师转行至大模型领域需要学习一系列新的技能和知识。以下是一个详细的…

【算法】JAVA刷算法必备数据结构

文章目录 数组List队列和栈栈的应用&#xff1a;表达式求值 数组List ArrayList 类是一个可以动态修改的数组&#xff0c;与普通数组的区别就是它是没有固定大小的限制&#xff0c;我们可以添加或删除元素。 ArrayList 继承了 AbstractList &#xff0c;并实现了 List 接口。 …

Nest.js实现一个简单的聊天室

本文将介绍如何使用 Nest.js 和 Uni-app 实现一个简单的实时聊天应用。后端使用 nestjs/websockets 和 socket.io&#xff0c;前端使用 uni-app 并集成 socket.io-client。这个项目允许多个用户同时加入聊天并实时交换消息。 效果图&#xff1a; 一、准备工作 安装 Node.js 和…

DAF-Net:一种基于域自适应的双分支特征分解融合网络用于红外和可见光图像融合

论文 DAF-Net: A Dual-Branch Feature Decomposition Fusion Network with Domain Adaptive for Infrared and Visible Image Fusion 提出了一种新的红外和可见光图像融合方法。该方法旨在结合红外图像和可见光图像的互补信息&#xff0c;以提供更全面的场景理解。红外图像在低…

学习C++的第七天!

1.虚函数是在基类中用 virtual 关键字声明的函数&#xff0c;可以在派生类中被重写。纯虚函数是在虚函数的基础上&#xff0c;在基类中被初始化为 0 的函数&#xff0c;含有纯虚函数的类是抽象类&#xff0c;不能被实例化。 2.如果基类的析构函数不是虚函数&#xff0c;当通过…