Java 异常
1 Exception的基本概念。
异常是Java中的一个对象,并定义一个基类java.lang.Throwable作为所有异常的超类。 Throwable类有两个直接子类: java.long. Error和java.long.Exception。Exception又有一个特殊的子类java.long.RuntimeException。
对于Error和Exception,很好区别这两个概念。Error是指程序在运行时发生了无法处理的错误,比如内存溢出等。发生错误时程序无法继续运行,Java虚拟机一般选择线程终止来处理错误。Exception是指发生了程序本身可以处理的错误。在java代码中指明Exception发生时如何处理。
RuntimeExceptio是Exception的一个子类。继承RuntimeException的异常我们通常叫做“非检查异常(Unchecked Exception)”,非继承RuntimeException的异常叫做“检查异常(Checked Exception)”。检查异常和非检查异常的区别在于:java编译器对检查异常有一定的要求,不符合要求将不能通过编译。一是如果函数要抛出检查异常,必须在函数声明部分用throws关键字指明要抛出的异常。二是如果代码调用的函数声明了可能抛出检查异常,调用点代码使用try/catch块去捕获该检查异常,如果不想捕获,也可以在调用代码的函数声明部重新声明要抛出该检查异常。而对于非检查异常和Error编译器不做检查。
必须在函数声明部分指明要抛出的检查异常
public class ClassA {
public void metohA() throws ExceptionA {
// ...
throw new ExceptionA();
}
}
捕获函数抛出的检查异常
public class ClassB {
public void metohB(){
try{
//...
ClassA classA= new ClassA();
classA.metohA();//metohA()抛出检查异常
//.....
}catch (ExceptionA ex) {
// ...
}
}
}
重新声明抛出异常
public class ClassB throws ExceptionA {
public void metohB(){
//...
ClassA classA= new ClassA();
classA.metohA();//metohA()声明了抛出检查异常。
//.....
}
}
}
Java API的设计者们建议尽可能的使用检查异常,以好借助编译器的检查来写出健壮的软件。但是这种做法也带来了一定的副作用。因为一些异常即使捕获也是无法处理的,比如对IOException。当发生对IOException时,大部分程序是无法处理的,但是你又不的不处理这个异常。处理这些异常给本来简单的代码带来了一定的晦涩,即使捕获该异常也不能进行处理,但是不加以捕获又可能更糟糕,因为编译器要求你的方法必须要处理这些异常。这样你的实施细则就不得不暴露在外了,而通常好的面向对象的设计都是要隐藏细节的。一些Java界的知名人物开始质疑Java的“检察的异常”的模型是否是一个失败的试验。对于是否是失败的试验,在我们对java异常做充分的了解后,自己会有一个合理的评判。
2 异常的设计原理。
什么时候我们应该抛出一个异常。或者我们应该如何设计一个异常。这取决于你的程序的业务逻辑。而不是在编码阶段才考虑程序中需要设计那些异常,是设计成检查异常还是非检查异常。异常来源于你的业务逻辑。
我们举例来说明如何设计异常。假设我们要做一个统一的登录系统,我们要设计一个登录检查类CheckingLogin。在这个类里,存在着用户的用户名、密码等信息。我们要判断一个用户是否能够登录系统。CheckingLogin类里check的方法会接受两个字符串userName和password为参数。正常的处理流程是用户和密码正确,用户可以登录。但是有两种特殊的情况,一是用户不存在,二是用户存在但密码错误。这三种情况都是正常的业务流程,check方法要处理这三种情况。
在设计中很自然的为上述两种意外情况设计两个异常一个是NoUserException(用户不存在异常),一个是PasswordErrorException(密码错误异常)。这两种异常在正常的操作中都会发生。调用者必须处理这些异常。为了使调用者不能忽略这些异常,很显然我们把这两种异常设计为检查异常,并反应在方法签名里。
检查异常看成是业务逻辑的一部分。是可以预期的,在正常操作过程中是可能发生的。在检查异常发生时,是可以指定对应的特殊流程进行处理。
还是上面的例子,在检查用户名称和密码的过程当中,我们可能需要访问数据库获得用户的信息,增加一个方访问数据库的方法getUser()。但是在访问数据库的过程当中,可能发生数据库忘记启动或连接数据库的网络中断的情况。当发现这种情况时,getUser()方法本身是无法处理的,调用者也无法处理。很自然应在这种情况发生时抛出一个非检查异常DataAccessException(数据访问异常)。
检查异常通常表示有故障发生,这种故障是无法预期的。只能通过人为或其他方式处理故障。设计检查异常的目的应该是记录故障信息,供解决故障时参考。
从上面的例子可以看出,检查异常通常表示一个特殊的处理流程。是可以预计和处理的。检查异常的主要作用是告诉调用者处理这种特殊流程。非检查异常通常表示发生了不该发生的故障,是无法预期的。非检查异常的作用是把故障信息记录下来,为解决故障的人提供信息。
3 对非检查异常的处理。
非检查异常对程序当中可能发生的故障的最好表达方式。非检查异常的主要作用是记录下故障信息。使用非检查异常处理故障,可以使上游的调用方法不需要为异常添加处理代码,从而减少了混乱。
对于非检查异常,一般不需要捕获和处理。捕获非检查异常不会是程序更加健壮,只是使程序晦涩难懂,干扰理解业务逻辑。那么非检查异常何时才捕获和处理?只需要最后设置一个非检查异常处理中心,在这里处理所有的非检查异常。在设计你的异常处理框架时,你应该让所有的非检查异常最后都能到达一个处理中心来进行处理,而不是在各个调用的地方。这样使编码更加简单并达到记录故障的目的。
在一个Strut2的应用里,故障处理中心可能是配置的一个异常拦截器,拦截RuntimeException和他的子类。在Spring MVC里,你可以继承SimpleMappingExceptionResolver类,并配置成处理RuntimeException和它的子类,来构建非检查异常处理中心。
4 对检查异常的处理。
检查异常标示特殊的处理流程。发生一个检查异常时,就发生了一个特殊的处理流程。当然我们也可以不用异常来表示特殊的处理流程。就上面系统登录的例子来说,我们可以让check方法返回true或false来表示是否验证通过,但是这样做丢失了一些信息,上层调用者不知道是因为用户不存在还是密码错误导致检查失败。或者我们可以返回一个字符串或数字来表示返回的信息,但是这样的话,我们就进入了错误代码检查的世界,而这正式Java异常模式所着力避免的。所以,使用异常可以使我们的方法返回更多和更加准确的信息。
因为返回一个异常就表示发生了一种特殊流程,所以要给不同的特殊流程定义不同的异常。好让上层调用者知道发生了什么。 对检查异常来说,记录下检查异常并没有太大的作用。只要上层捕获并处理异常,检查异常的目的就达到了,并不需要记录下来。
因为检查异常必须由调用者处理,会给编写代码带来额外的负担。如果一个检查异常调用者不能处理,可以把该异常继续抛出,将处理权交给更上层的调用者。如果能确定这个检查异常没有能够处理的可能,这时候把检查异常转换为非检查异常是一个不错的选择。以减轻上层调用者的负担。比如在SpringDAO中,Spring提供了一种方便的方法,把特定于某种技术的异常,如SQLException, 转化为自己的非检查异常。而不再需要在DAO中编写讨厌的样板式的catch/throw代码块和异常声明。
1 Exception的基本概念。
异常是Java中的一个对象,并定义一个基类java.lang.Throwable作为所有异常的超类。 Throwable类有两个直接子类: java.long. Error和java.long.Exception。Exception又有一个特殊的子类java.long.RuntimeException。
对于Error和Exception,很好区别这两个概念。Error是指程序在运行时发生了无法处理的错误,比如内存溢出等。发生错误时程序无法继续运行,Java虚拟机一般选择线程终止来处理错误。Exception是指发生了程序本身可以处理的错误。在java代码中指明Exception发生时如何处理。
RuntimeExceptio是Exception的一个子类。继承RuntimeException的异常我们通常叫做“非检查异常(Unchecked Exception)”,非继承RuntimeException的异常叫做“检查异常(Checked Exception)”。检查异常和非检查异常的区别在于:java编译器对检查异常有一定的要求,不符合要求将不能通过编译。一是如果函数要抛出检查异常,必须在函数声明部分用throws关键字指明要抛出的异常。二是如果代码调用的函数声明了可能抛出检查异常,调用点代码使用try/catch块去捕获该检查异常,如果不想捕获,也可以在调用代码的函数声明部重新声明要抛出该检查异常。而对于非检查异常和Error编译器不做检查。
必须在函数声明部分指明要抛出的检查异常
public class ClassA {
public void metohA() throws ExceptionA {
// ...
throw new ExceptionA();
}
}
捕获函数抛出的检查异常
public class ClassB {
public void metohB(){
try{
//...
ClassA classA= new ClassA();
classA.metohA();//metohA()抛出检查异常
//.....
}catch (ExceptionA ex) {
// ...
}
}
}
重新声明抛出异常
public class ClassB throws ExceptionA {
public void metohB(){
//...
ClassA classA= new ClassA();
classA.metohA();//metohA()声明了抛出检查异常。
//.....
}
}
}
Java API的设计者们建议尽可能的使用检查异常,以好借助编译器的检查来写出健壮的软件。但是这种做法也带来了一定的副作用。因为一些异常即使捕获也是无法处理的,比如对IOException。当发生对IOException时,大部分程序是无法处理的,但是你又不的不处理这个异常。处理这些异常给本来简单的代码带来了一定的晦涩,即使捕获该异常也不能进行处理,但是不加以捕获又可能更糟糕,因为编译器要求你的方法必须要处理这些异常。这样你的实施细则就不得不暴露在外了,而通常好的面向对象的设计都是要隐藏细节的。一些Java界的知名人物开始质疑Java的“检察的异常”的模型是否是一个失败的试验。对于是否是失败的试验,在我们对java异常做充分的了解后,自己会有一个合理的评判。
2 异常的设计原理。
什么时候我们应该抛出一个异常。或者我们应该如何设计一个异常。这取决于你的程序的业务逻辑。而不是在编码阶段才考虑程序中需要设计那些异常,是设计成检查异常还是非检查异常。异常来源于你的业务逻辑。
我们举例来说明如何设计异常。假设我们要做一个统一的登录系统,我们要设计一个登录检查类CheckingLogin。在这个类里,存在着用户的用户名、密码等信息。我们要判断一个用户是否能够登录系统。CheckingLogin类里check的方法会接受两个字符串userName和password为参数。正常的处理流程是用户和密码正确,用户可以登录。但是有两种特殊的情况,一是用户不存在,二是用户存在但密码错误。这三种情况都是正常的业务流程,check方法要处理这三种情况。
在设计中很自然的为上述两种意外情况设计两个异常一个是NoUserException(用户不存在异常),一个是PasswordErrorException(密码错误异常)。这两种异常在正常的操作中都会发生。调用者必须处理这些异常。为了使调用者不能忽略这些异常,很显然我们把这两种异常设计为检查异常,并反应在方法签名里。
检查异常看成是业务逻辑的一部分。是可以预期的,在正常操作过程中是可能发生的。在检查异常发生时,是可以指定对应的特殊流程进行处理。
还是上面的例子,在检查用户名称和密码的过程当中,我们可能需要访问数据库获得用户的信息,增加一个方访问数据库的方法getUser()。但是在访问数据库的过程当中,可能发生数据库忘记启动或连接数据库的网络中断的情况。当发现这种情况时,getUser()方法本身是无法处理的,调用者也无法处理。很自然应在这种情况发生时抛出一个非检查异常DataAccessException(数据访问异常)。
检查异常通常表示有故障发生,这种故障是无法预期的。只能通过人为或其他方式处理故障。设计检查异常的目的应该是记录故障信息,供解决故障时参考。
从上面的例子可以看出,检查异常通常表示一个特殊的处理流程。是可以预计和处理的。检查异常的主要作用是告诉调用者处理这种特殊流程。非检查异常通常表示发生了不该发生的故障,是无法预期的。非检查异常的作用是把故障信息记录下来,为解决故障的人提供信息。
3 对非检查异常的处理。
非检查异常对程序当中可能发生的故障的最好表达方式。非检查异常的主要作用是记录下故障信息。使用非检查异常处理故障,可以使上游的调用方法不需要为异常添加处理代码,从而减少了混乱。
对于非检查异常,一般不需要捕获和处理。捕获非检查异常不会是程序更加健壮,只是使程序晦涩难懂,干扰理解业务逻辑。那么非检查异常何时才捕获和处理?只需要最后设置一个非检查异常处理中心,在这里处理所有的非检查异常。在设计你的异常处理框架时,你应该让所有的非检查异常最后都能到达一个处理中心来进行处理,而不是在各个调用的地方。这样使编码更加简单并达到记录故障的目的。
在一个Strut2的应用里,故障处理中心可能是配置的一个异常拦截器,拦截RuntimeException和他的子类。在Spring MVC里,你可以继承SimpleMappingExceptionResolver类,并配置成处理RuntimeException和它的子类,来构建非检查异常处理中心。
4 对检查异常的处理。
检查异常标示特殊的处理流程。发生一个检查异常时,就发生了一个特殊的处理流程。当然我们也可以不用异常来表示特殊的处理流程。就上面系统登录的例子来说,我们可以让check方法返回true或false来表示是否验证通过,但是这样做丢失了一些信息,上层调用者不知道是因为用户不存在还是密码错误导致检查失败。或者我们可以返回一个字符串或数字来表示返回的信息,但是这样的话,我们就进入了错误代码检查的世界,而这正式Java异常模式所着力避免的。所以,使用异常可以使我们的方法返回更多和更加准确的信息。
因为返回一个异常就表示发生了一种特殊流程,所以要给不同的特殊流程定义不同的异常。好让上层调用者知道发生了什么。 对检查异常来说,记录下检查异常并没有太大的作用。只要上层捕获并处理异常,检查异常的目的就达到了,并不需要记录下来。
因为检查异常必须由调用者处理,会给编写代码带来额外的负担。如果一个检查异常调用者不能处理,可以把该异常继续抛出,将处理权交给更上层的调用者。如果能确定这个检查异常没有能够处理的可能,这时候把检查异常转换为非检查异常是一个不错的选择。以减轻上层调用者的负担。比如在SpringDAO中,Spring提供了一种方便的方法,把特定于某种技术的异常,如SQLException, 转化为自己的非检查异常。而不再需要在DAO中编写讨厌的样板式的catch/throw代码块和异常声明。