本文基于笔者自身的学习程度来浅谈一下Java的异常及其通常的处理方法。什么是异常?java
Java程序运行时,发生的不被指望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常。程序员
Java标准库内建了一些通用的异常,这些类以Throwable为顶层父类。全部的异常都是从Throwable继承而来的,是全部异常的共同祖先。数据库
Error类以及他的子类的实例,表明了JVM自己的错误。错误不能被程序员经过代码处理,Error不多出现。所以,程序员应该关注Exception为父类的分支下的各类异常类。编程
Exception以及他的子类,表明程序运行时发送的各类不指望发生的事件。能够被Java异常处理机制使用,是异常处理的核心。数组
非检查异常。Error和RuntimeException以及他们的子类。javac在编译时,不会提示和发现这样的异常,不要求在程序处理这些异常。因此若是愿意,咱们能够编写代码处理(使用try…catch…finally)这样的异常,也能够不处理。对于这些异常,咱们应该修正代码,而不是去经过异常处理器处理。这样的异常发生的缘由多半是代码写的有问题。如除0错误ArithmeticException,错误的强制类型转换错误ClassCastException,数组索引越界ArrayIndexOutOfBoundsException,使用了空对象NullPointerException等等。bash
检查异常。除了Error 和 RuntimeException的其它异常。可检查的异常,这是编码时很是经常使用的,全部checked exception都是须要在代码中处理的。它们的发生是能够预测的,正常的一种状况,能够合理的处理。java强制要求程序员为这样的异常作预备处理工做(使用try…catch…finally或者throws)。在方法中要么用try-catch语句捕获它并处理,要么用throws子句声明抛出它,不然编译不会经过。这样的异常通常是由程序的运行环境致使的。由于程序可能被运行在各类未知的环境下,而程序员没法干预用户如何使用他编写的程序,因而程序员就应该为这样的异常时刻准备着。如SQLException , IOException,ClassNotFoundException 等。多线程
在编写代码处理异常时,对于检查异常,有2种不一样的处理方式:使用try…catch…finally语句块处理它。或者,在函数签名中使用throws 声明交给函数调用者caller去解决。模块化
try{
//try块中放可能发生异常的代码。
//若是执行完try且不发生异常,则接着去执行finally块和finally后面的代码(若是有的话)。
//若是发生异常,则尝试去匹配catch块。
}catch(SQLException SQLexception){
//每个catch块用于捕获并处理一个特定的异常,或者这异常类型的子类。Java7中能够将多个异常声明在一个catch中。
//catch后面的括号定义了异常类型和异常参数。若是异常与之匹配且是最早匹配到的,则虚拟机将使用这个catch块来处理异常。
//在catch块中可使用这个块的异常参数来获取异常的相关信息。异常参数是这个catch块中的局部变量,其它块不能访问。
//若是当前try块中发生的异常在后续的全部catch中都没捕获到,则先去执行finally,而后到这个函数的外部caller中去匹配异常处理器。
//若是try中没有发生异常,则全部的catch块将被忽略。
}catch(Exception exception){
//...
}finally{
//finally块一般是可选的。
//不管异常是否发生,异常是否匹配被处理,finally都会执行。
//一个try至少要有一个catch块,不然, 至少要有1个finally块。可是finally不是用来处理异常的,finally不会捕获异常。
//finally主要作一些清理工做,如流的关闭,数据库链接的关闭等。
}
复制代码
一、try块中的局部变量和catch块中的局部变量(包括异常变量),以及finally中的局部变量,他们之间不可共享使用。函数
二、每个catch块用于处理一个异常。异常匹配是按照catch块的顺序从上往下寻找的,只有第一个匹配的catch会获得执行。匹配时,不只运行精确匹配,也支持父类匹配,所以,若是同一个try块下的多个catch异常类型有父子关系,应该将子类异常放在前面,父类异常放在后面,这样保证每一个catch块都有存在的意义。学习
三、java中,异常处理的任务就是将执行控制流从异常发生的地方转移到可以处理这种异常的地方去。也就是说:当一个函数的某条语句发生异常时,这条语句的后面的语句不会再执行,它失去了焦点。执行流跳转到最近的匹配的异常处理catch代码块去执行,异常被处理完后,执行流会接着在“处理了这个异常的catch代码块”后面接着执行。
public static void main(String[] args){
int a=8/0; //异常抛出点
System.out.println("处理异常");
}
复制代码
public static void main(String[] args){
try {
int a=8/0; //异常抛出点
}catch(ArithmeticException e) {
System.out.println("处理异常");
}
}
复制代码
finally块无论异常是否发生,只要对应的try执行了,则它必定也执行。只有一种方法让finally块不执行:System.exit()。所以finally块一般用来作资源释放操做:关闭文件,关闭数据库链接等等。
良好的编程习惯是:在try块中打开资源,在finally块中清理释放这些资源。
须要注意的地方:
一、finally块没有处理异常的能力。处理异常的只能是catch块。
二、在同一try…catch…finally块中,若是try中抛出异常,且有匹配的catch块,则先执行catch块,再执行finally块。若是没有catch块匹配,则先执行finally,而后去外面的调用者中寻找合适的catch块。
三、在同一try…catch…finally块中,try发生异常,且匹配的catch块中处理异常时也抛出异常,那么后面的finally也会执行:首先执行finally块,而后去外围调用者中寻找合适的catch块。
throws声明:若是一个方法内部的代码会抛出检查异常(checked exception),而方法本身又没有彻底处理掉,则java保证你必须在方法的签名上使用throws关键字声明这些可能抛出的异常,不然编译不经过。
throws是另外一种处理异常的方式,它不一样于try…catch…finally,throws仅仅是将函数中可能出现的异常向调用者声明,而本身则不具体处理。
采起这种异常处理的缘由多是:方法自己不知道如何处理这样的异常,或者说让调用者处理更好,调用者须要为可能发生的异常负责。
public void throwBibi() throws ExceptionType1 , ExceptionType2 ,ExceptionTypeN
{
/*throwBibi内部能够抛出 ExceptionType1 , ExceptionType2 ,
ExceptionTypeN 类的异常,或者他们的子类的异常对象。*/
}
复制代码
注意事项 一、若是是非检查异常(unchecked exception),即Error、RuntimeException或它们的子类,那么能够不使用Throws关键字来声明要抛出的异常,编译仍能顺利经过,但在运行时会被系统抛出。
二、当抛出了异常,则该方法的调用者必须处理或者从新抛出该异常。
三、当子类重写父类抛出异常的方法时,声明的异常必须是父类方法所声明异常的同类或子类。
throw 异常抛出语句 throw exceptionObject
程序员也能够经过throw语句手动显式的抛出一个异常。throw语句的后面必须是一个异常对象。
throw 语句必须写在函数中,执行throw语句的地方就是一个异常抛出点,它和由JRE自动造成的异常抛出点没有任何差异。
public void save(User user)
{
if(user == null)
throw new IllegalArgumentException("User对象为空");
//......
}
复制代码
throw与throws的区别
一、throw用在方法体内,上面代码显示了,是直接在方法体内。 throws用在方法声明后面,表示再抛出异常,由该方法的调用者来处理。
二、throw是具体向外抛异常的,抛出的是一个异常实例。 throws声明了是哪一种类型的异常,使它的调用者能够捕获这个异常。
三、throw,若是执行了,那么必定是抛出了某种异常了,但throws表示可能出现,但不必定。
四、同时出现的时候,throws出如今函数头、throw出如今函数体,两种不会由函数去处理,真正的处理由函数的上层调用处理。
所谓自定义异常,就是定义一个类,去继承Throwable类或者它的子类。
使用Java内置的异常类能够描述在编程时出现的大部分异常状况,也能够经过自定义异常描述特定业务产生的异常类型。
若是要自定义异常类,则扩展Exception类便可,所以这样的自定义异常都属于检查异常(checked exception)。若是要自定义非检查异常,则扩展自RuntimeException。
按照国际惯例,自定义的异常应该老是包含以下的构造函数:
一个无参构造函数。 一个带有String参数的构造函数,并传递给父类的构造函数。 一个带有String参数和Throwable参数,并都传递给父类构造函数 一个带有Throwable 参数的构造函数,并传递给父类的构造函数。
下面是IOException类的完整源代码,能够借鉴。
public class IOException extends Exception
{
static final long serialVersionUID = 7818375828146090155L;
public IOException()
{
super();
}
public IOException(String message)
{
super(message);
}
public IOException(String message, Throwable cause)
{
super(message, cause);
}
public IOException(Throwable cause)
{
super(cause);
}
}
复制代码
自定义异常的注意事项 一、当子类重写父类的带有 throws声明的函数时,其throws声明的异常必须在父类异常的可控范围内——用于处理父类的throws方法的异常处理器,必须也适用于子类的这个带throws方法 。这是为了支持多态。
例如,父类方法throws 的是2个异常,子类就不能throws 3个及以上的异常。父类throws IOException,子类就必须throws IOException或者IOException的子类。
二、Java程序能够是多线程的。每个线程都是一个独立的执行流,独立的函数调用栈。若是程序只有一个线程,那么没有被任何代码处理的异常会致使程序终止。若是是多线程的,那么没有被任何代码处理的异常仅仅会致使异常所在的线程结束。
也就是说,Java中的异常是线程独立的,线程的问题应该由线程本身来解决,而不要委托到外部,也不会直接影响到其它线程的执行。
在一些大型的,模块化的软件开发中,有时咱们会捕获到一个异常后再抛出另外一个异常。这样将异常的发生缘由一个传一个串起来,即把底层的异常信息传给上层,这样逐层抛出,称之为异常链化。
异常链化:以一个异常对象为参数构造新的异常对象。新的异对象将包含先前异常的信息。这项技术主要是异常类的一个带Throwable参数的函数来实现的。这个当作参数的异常,咱们叫他根源异常(cause)。
查看Throwable类源码,能够发现里面有一个Throwable字段cause,就是它保存了构造时传递的根源异常参数。这种设计和链表的结点类设计一模一样,所以造成链也是天然的了
public class Throwable implements Serializable {
private Throwable cause = this;
public Throwable(String message, Throwable cause) {
fillInStackTrace();
detailMessage = message;
this.cause = cause;
}
public Throwable(Throwable cause) {
fillInStackTrace();
detailMessage = (cause==null ? null : cause.toString());
this.cause = cause;
}
//........
}
复制代码
下面是一个例子,演示了异常的链化:从命令行输入2个int,将他们相加,输出。输入的数不是int,则致使getInputNumbers异常,从而致使add函数异常,则能够在add函数中抛出一个链化的异常。
public static void main(String[] args)
{
System.out.println("请输入2个加数");
int result;
try
{
result = add();
System.out.println("结果:"+result);
} catch (Exception e){
e.printStackTrace();
}
}
//获取输入的2个整数返回
private static List<Integer> getInputNumbers()
{
List<Integer> nums = new ArrayList<>();
Scanner scan = new Scanner(System.in);
try {
int num1 = scan.nextInt();
int num2 = scan.nextInt();
nums.add(new Integer(num1));
nums.add(new Integer(num2));
}catch(InputMismatchException immExp){
throw immExp;
}finally {
scan.close();
}
return nums;
}
//执行加法计算
private static int add() throws Exception
{
int result;
try {
List<Integer> nums =getInputNumbers();
result = nums.get(0) + nums.get(1);
}catch(InputMismatchException immExp){
throw new Exception("计算失败",immExp); //链化:以一个异常对象为参数构造新的异常对象。
}
return result;
}
复制代码
结果
在程序运行过程当中,意外发生的状况,背离咱们程序自己的意图的表现,均可以理解为异常。
Java提供的异常机制能够更好的提高程序的健壮性,是一Java学习过程当中必须掌握的一项基础。
只是浅谈,可能存在缺漏或错误的地方,望谅解。
愿君莫秃了头