Java异常机制及异常处理建议
1、Java异常机制
异常指不期而至的各种状况,如:文件找不到、网络连接失败、非法参数等。异常是一个事件,它发生在程序运行期间,干扰了正常的指令流程。Java通过API中Throwable类的众多子类描述各种不同的异常。因而,Java异常都是对象,是Throwable子类的实例,描述了出现在一段编码中的错误条件。当条件生成时,错误将引发异常。
Java异常类层次结构图:
在Java中,所有的异常都有一个共同的祖先Throwable(可抛出)类。Throwable指定代码中可用异常传播机制通过Java应用程序传输的任何问题的共性。
Throwable有两个重要的子类:Exception(异常)和Error(错误),二者都是Java异常处理的重要子类,各自都包含大量子类。
Java异常的分类
Error(错误):是程序无法处理的错误,表示运行应用程序时出现的较严重的问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时JVM(Java虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual Machine Error),当JVM不再有继续执行操作所需的内存资源时,将出现OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如Java虚拟机运行错误(Virtual Machine Error)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在Java中,错误通过Error的子类描述。
Exception(异常):是程序本身可以处理的异常。Exception类有一个重要的子类RuntimeException。RuntimeException类及其子类表示“JVM常用操作”引发的错误。例如,若在程序中试图使用空值对象的引用、除数为零或数组访问越界,则分别引发运行时异常——NullPointerException、ArithmeticException和ArrayIndexOutOfBoundException。
注意:Exception和Error的区别,Exception能被程序本身可以处理,Error无法被程序处理。
通常,Java的异常(包括Exception和Error)分为可查的异常(checked exceptions)和不可查的异常(unchecked exceptions)。
可查异常(checked exceptions):是编译器要求必须处置的异常,正确的程序在运行过程中,很容易出现情理可容的异常状况。可查异常虽然是异常状况,但在一定程度上它的发生是可以预计的,而且一旦发生这种异常状况,就必须采取某种方式进行处理。除了RuntimeException及其子类以外,其他的Exception类及其子类都属于可查异常,最典型的是IO类异常。这种异常的特点是Java编译器会检查它,也就是说,当程序中出现了这类异常时,要么用try-catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过。
不可查异常(unchecked exceptions):编译器不要求强制处置的异常,包括运行时异常,即RuntimeException与其子类和错误(Error)。排除Error的情况,还可以将所有的Exception分为两大类:运行时异常和非运行时异常(非运行时异常也叫编译异常)。程序中应当尽可能的去处理这些异常。
运行时Exception与非运行时Exception
运行时异常:都是RuntimeException类及其子类异常,如空指针异常、下标越界异常等,这些异常是不可查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也能编译通过。我们可以不处理运行时异常,当出现这样的异常时,总是由虚拟机接管。比如:我们从来没有人去处理过NullPointerException异常,它就是运行时异常,并且这种异常还是最常见的异常之一。出现运行时异常后,如果没有捕获并处理这个异常(即没有catch),系统会把异常一直往上层抛,一直到最上层,如果是多线程就由Thread.run()抛出,如果是单线程就被main()抛出。抛出之后,如果是线程,这个线程也就退出了。如果是主程序抛出的异常,那么这整个程序也就退出了。也就是说,你如果不对运行时异常进行处理,那么出现运行时异常之后,要么是线程中止,要么是主程序终止。
非运行时异常(编译异常):是RuntimeException以外的异常,类型上也属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,对于这种异常,JAVA编译器强制要求我们必需对出现的这些异常进行catch并处理,否则程序就不能编译通过。所以,面对这种异常不管我们是否愿意,只能自己去写一大堆catch块去处理可能的异常。
2、异常处理实践
Java中的异常处理不是一个简单的话题。初学者很难理解,甚至有经验的开发人员也会花几个小时来讨论应该如何抛出或处理这些异常。这就是为什么大多数开发团队都有自己的异常处理的规则和方法。如果你是一个团队的新手,你可能会惊讶于这些方法与你之前使用过的那些方法有多么不同。
在Finally中清理资源
通常情况下,你在try中使用了一个资源,比如InputStream,之后需要关闭它。在这种情况下,一个常见的错误是在try的末尾关闭了资源。
public void doNotCloseResourceInTry() {
FileInputStream inputStream = null;
try {
File file = new File("./tmp.txt");
inputStream = new FileInputStream(file);
// use the inputStream to read a file
// do NOT do this
inputStream.close();
} catch (FileNotFoundException e) {
log.error(e);
} catch (IOException e) {
log.error(e);
}
}
问题是,只要不抛出异常,这种方法就可以很好地运行。try内的所有语句都将被执行,资源也会被关闭。但是你在try里调用了一个或多个可能抛出异常的方法,或者自己抛出异常。这意味着可能无法到达try的末尾。因此,将不会关闭这些资源。所以应该将清理资源的代码放入Finally中,或者使用Try-With-Resource语句。
相比于try,无论是在成功执行try里的代码后,或是在catch中处理了一个异常后,Finally里的内容是一定会被执行的。因此,可以确保清理所有已打开的资源。
public void closeResourceInFinally() {
FileInputStream inputStream = null;
try {
File file = new File("./tmp.txt");
inputStream = new FileInputStream(file);
// use the inputStream to read a file
} catch (FileNotFoundException e) {
log.error(e);
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
log.error(e);
}
}
}
}
不要捕获继承自RuntimeException的异常
继承自RuntimeException异常是运行时异常,如:IndexOutOfBoundsException、NullPointerException,这类异常由程序员预检查来规避,保证程序健壮性。
//正例
if(obj != null) {
...
}
//反例:
try {
obj.method()
} catch(NullPointerException e) {
...
}
给出准确的异常处理信息
你抛出的异常越具体越好。一定要记住,一个不太了解你代码的同事,也许几个月后,需要调用你的方法,并且处理这个异常。
因此,请确保提供尽可能多的信息,这会使你的API更容易理解。因此,你方法的调用者将能够更好地处理异常,或者通过额外的检查来避免它。
所以,要尽量能更好地描述你的异常处理信息,比如用NumberFormatException代替IllegalArgumentException,避免抛出一个不具体的异常。
public void doNotDoThis() throws Exception {
...
}
public void doThis() throws NumberFormatException {
...
}
记录你所指定的异常
当你在方法中指定一个异常时,你应该在Javadoc中记录下它。这与前面提到的方法有着相同的目标:为调用者提供尽可能多的信息,这样他们就可以避免异常或者更容易地处理异常。
因此,请确保在Javadoc中添加一个@throws 声明,并描述可能导致的异常情况。
/**
* This method does something extremely useful ...
*
* @param input
* @throws MyBusinessException if ... happens
*/
public void doSomething(String input) throws MyBusinessException {
...
}
使用描述性消息抛出异常
这一理念与前两个相似。但这一次,你不用给调用方法的人提供信息。异常消息会被所有人读取,同时必须了解在日志文件或监视工具中报告异常时发生了什么。
如果抛出一个特定的异常,它的类名很可能已经描述了这种类型的错误。所以,你不需要提供很多额外的信息。一个很好的例子就是,当你以错误的格式使用字符串时,如NumberFormatException,它就会被类 java.lang.Long的构造函数抛出。
try {
new Long("xyz");
} catch (NumberFormatException e) {
log.error(e);
}
NumberFormatException已经告诉你问题的类型,所以只需要提供导致问题的输入字符串。如果异常类的名称不具有表达性,那么就需要提供必要的解释信息。
17:17:26,386 ERROR TestExceptionHandling:52 - java.lang.NumberFormatException: For input string: "xyz"
不要在catch中使用Throwable
Throwable是exceptions 和 errors的父类。当然,你可以在catch子句中使用它,但其实你不应该这样做。
如果你在catch子句中使用Throwable,它将不仅捕获所有的异常(Exception),还会捕获所有错误(Error)。JVM会抛出错误,这是应用程序不打算处理的严重问题。典型的例子是OutOfMemoryError或StackOverflowError。这两种情况都是由应用程序控制之外的情况引起的,无法处理。
所以,最好不要在catch中使用Throwable,除非你完全确定自己处于一个特殊的情况下,并且你需要处理一个错误。
public void doNotCatchThrowable() {
try {
// do something
} catch (Throwable t) {
// don't do this!
}
}
不要在catch中记录异常然后又抛出这个异常
你可以在许多代码片段或者库文件里发现,有异常会被捕获、记录和重新抛出。
try {
new Long("xyz");
} catch (NumberFormatException e) {
log.error(e);
throw e;
}
当它发生时,记录一个异常,然后重新抛出它,以便调用者能够适当地处理它,这可能会很直观。但是它会为同一个异常写多个错误消息。
17:44:28,945 ERROR TestExceptionHandling:65 - java.lang.NumberFormatException: For input string: "xyz"
Exception in thread "main" java.lang.NumberFormatException: For input string: "xyz"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Long.parseLong(Long.java:589)
at java.lang.Long.(Long.java:965)
at com.stackify.example.TestExceptionHandling.logAndThrowException(TestExceptionHandling.java:63)
at com.stackify.example.TestExceptionHandling.main(TestExceptionHandling.java:58)
如果你需要添加额外的信息,应该捕获异常并将其包装在一个自定义的信息中。有时最好捕获一个标准异常并将其封装到一个定制的异常中。此类异常的典型例子是应用程序或框架特定的业务异常。这允许你添加额外的信息,并且也可以为异常类实现一个特殊的处理。
public void wrapException(String input) throws MyBusinessException {
try {
// do something
} catch (NumberFormatException e) {
throw new MyBusinessException("A message that describes the error.", e);
}
}
将异常抛给函数调用者
如果一个方法可能会出现异常,但其自身却没有能力处理这种异常,可以在方法声明处用throws子句来声明抛出异常,由上层的方法调用者处理。例如汽车在运行时可能会出现故障,汽车本身没办法处理这个故障,那就让开车的人来处理。throws语句用在方法定义时声明该方法要抛出的异常类型,如果抛出的是Exception异常类型,则该方法被声明为抛出所有的异常。多个异常可使用逗号分隔。throws语句的语法格式为:
void fun() throws Exception1,Exception2,..,ExceptionN {
}
方法名后的throws Exception1,Exception2,…,ExceptionN为声明要抛出的异常列表。当方法抛出了异常列表中的异常时,方法将不对这些类型及其子类类型的异常作处理,而抛向上层调用该方法的方法,由它去处理。
如果在函数体内用throw抛出了某种异常,最好要在函数名中加throws抛异常声明,以便交给调用它的上层函数进行处理。捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它 的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容
只将非稳定代码放在try-catch块中
一大段代码进行try-catch,这是不负责任的表现。catch时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的catch,要尽可能地进行区分异常类型,再做对应的异常处理。
不要在finally中写return语句
不能在 finally 块中使用 return,finally块中的return返回后方法结束执行,不会再执行try块中的return语句。
Java异常机制及异常处理建议的更多相关文章
- Atitit 异常机制与异常处理的原理与概论
Atitit 异常机制与异常处理的原理与概论 1. 异常vs 返回码1 1.1. 返回码模式的处理 (瀑布if 跳到失败1 1.2. 终止模式 vs 恢复模式(asp2 1.3. 异常机制的设计原理 ...
- 全面理解java异常机制
在理想状态下,程序会按照我们预想的步骤一步一步的执行,但是即使你是大V,你也不可避免出错,所以java为我们提供了异常机制.本文将会从以下几个方面介绍java中的异常机制: 异常机制的层次结构 异常的 ...
- Java 异常机制
Java 异常机制 什么是异常 异常指不期而至的各种状况,如:文件找不到.网络连接失败.非法参数等.异常是一个事件,它发生在程序运行期间,干扰了正常的指令流程 为什么要有异常 什么出错了 哪里出错了 ...
- 【55】java异常机制剖析
一.为什么要使用异常 首先我们可以明确一点就是异常的处理机制可以确保我们程序的健壮性,提高系统可用率.虽然我们不是特别喜欢看到它,但是我们不能不承认它的地位,作用.有异常就说明程序存在问题,有助于我们 ...
- [Java学习笔记] Java异常机制(也许是全网最独特视角)
Java 异常机制(也许是全网最独特视角) 一.Java中的"异常"指什么 什么是异常 一句话简单理解:异常是程序运行中的一些异常或者错误. (纯字面意思) Error类 和 Ex ...
- Java异常机制
Java异常分类 异常表明程序运行发生了意外,导致正常流程发生错误,例如数学上的除0,打开一个文件但此文件实际不存在,用户输入非法的参数等.在C语言中我们处理这类事件一般是将其与代码正常的流程放在一起 ...
- Java基础 -- 深入理解Java异常机制
异常指不期而至的各种状况,如:文件找不到.网络连接失败.非法参数等.异常是一个事件,它发生在程序运行期间,干扰了正常的指令流程.Java通 过API中Throwable类的众多子类描述各种不同的异常. ...
- [学习笔记]Java异常机制
概述 异常 程序在执行时出现的不正常情况,是对问题的描写叙述.将问题进行对象的封装. Java中的异常,就是对不正常情况进行描写叙述后的对象体现. 异常体系 Throwable |--Erro ...
- 一文了解java异常机制
1.异常的概述 1.1什么是异常? 异常:程序在运行过程中发生由于外部问题导致的程序异常事件,发生的异常会中断程序的运行.(在Java等面向对象的编程语言中)异常本身是一个对象,产生异常就是产生了一个 ...
随机推荐
- Django使用本机IP无法访问,使用127.0.0.1能正常访问
使用Django搭建web站点后,使用127.0.0.1能访问,但是用自己本机IP却无法访问. 我们先到Django项目中找到setting文件 找到——> ALLOWED_HOSTS = [] ...
- TypeScript入门实例
前言 TypeScript是JavaScript的超集,微软公司开发,利用es6语法,实现对js的面向对象编程思想,写代码的时候会像强类型语言一样,指定参数类型.返回值类型,类型不对会报错,但编译后还 ...
- Maven安装和配置环境变量
Maven配置 1.下载 下载maven 3.5.4 先到官网http://maven.apache.org/download.cgi 下载最新版本(目前是3.5.4 ),下载完成后,解压到某个目录( ...
- SQLServer2000同步复制技术实现步骤
SQLServer2000同步复制技术实现步骤 一. 预备工作 1.发布服务器,订阅服务器都创建一个同名的windows用户,并设置相同的密码,做为发布快照文件夹的有效访问用户 --管理工具 --计算 ...
- S2:ArrayList
1.ArrayList ArrayList非常类似于数组,也有人称它为数组列表,ArrayList可以动态维护. 因为数组的长度是固定的,而SArrayList的容量可以根据需要自动扩充. Arr ...
- Robotframework获取移动端toast问题
背景: 在做移动端自动化测试的时候,经常会遇到一个问题就是获取toast提示问题,如果需要解决这个问题需要重新处理,不能按照正常的逻辑,使用robotframework自带的关键字进行获取,需要重新考 ...
- js 数组对象深拷贝
js 数组对象深拷贝 结论:对象的拷贝不能采用直接赋值的方式. 背景 踩过的坑如下: formData本来是父组件传过来的,但是我不想直接用,于是我直接赋值给一个formDataCopy的对象. 但是 ...
- 分享一个非常好用又好看的终端工具--Hyper (支持windows、MacOS、Linux)
分享一个非常好用又好看的终端工具--Hyper 官网地址: https://hyper.is/ 打开官网,选择对应版本安装即可:(可能网络原因,无法下载, 可以从我分享的链接下载 链接: https: ...
- vue中的虚拟DOM树
什么是虚拟DOM树?(Virtual DOM) 虚拟DOM树其实就是一个普通的js对象,它是用来描述一段HTML片段的 01 当页面渲染的时候Vue会创建一颗虚拟DOM树 02 ...
- Xlistview_聚合菜谱大全数据
public class MainActivity extends AppCompatActivity implements XListView.IXListViewListener{ private ...