您的位置:首页 > 编程语言 > Java开发

Java 项目中如何使用异常

2013-08-15 10:25 435 查看
1. 早抛出,晚捕获.

2. 如果 finally 语句中有 return 语句,则 finally 中的 return 语句将会覆盖 try 中的 return 语句,如以下代码,将会输出 1。如果在 finally 语句里有抛出异常,那么此异常将会覆盖 try 块中抛出的异常。

01
public
class
FinallyReturnTest
 {
02
public
static
void
main(String[]
 args) {
03
System.out.println(testFinallyReturn(
1
));
04
}
05
06
public
static
int
testFinallyReturn(
int
n)
 {
07
try
{
08
//
 do something
09
return
n+n;
10
}
finally
{
11
return
n;
12
}
13
}
14
}
15
/*
 output -->
16
1
17
*/
3. 进行文件IO/网络操作时,最好按以下形式使用 try/catch 块(摘自《Java 核心技术 卷1》 P480),以确保资源能被正确释放。

1
InputStream
 in = ...;
2
try
{
3
try
{
4
//
 code that might throw exceptions }finally{
5
in.close();
6
}
7
}
catch
(IOException
 e){
8
//
 show error dialog
9
}
4. 如果覆盖父类的方法,而父类的方法没有抛出异常,那么子类中的方法就必须捕获自己内部代码中出现的每一个异常。即,不允许子类的 throws 声明符中出现超过父类方法所声明的异常类范围。

5. 实现一个函数时,怎么判断这个函数内 catch 到的异常是再次 throw 出去,还是就地处理呢?

答:如果这个函数引发的异常,需要被外界(使用程序的人)所知,就需要 throw 出去,否则,就地处理。

6. 异常最好在需要向外界返回信息时进行处理(即与外界进行交互的第一层处,因为此时把导常发生的原因报告出去才是有意义的);

如一个按钮的 click 事件处理函数里面,直接写 try/catch,在 try 里调用相关处理函数,在 catch 里进行处理Exception;即内部调用的时候,都只要 throw 即可.

7. 当准备把异常传给调用方时,要确保异常的抽象层次与子程序接口的抽像层次是一致的(摘自《代码大全2》P200)

下面代码是一个抛出抽象层次不一致的异常的类:

1
class
Emoyee
 {
2
...
3
public
TaxId
 GetTaxId()
throws
EOFException
 {
4
...
5
}
6
...
7
}
GetTaxId() 把更低层的 EOFException(文件结束,end of file)异常返回给它的调用方。它本身并不拥有这一异常,但却通过把更低层次的异常传递给调用方,暴露了自身的一些实现细节。这就使得子程序的调用方法代码不是与 Employee 类的代码耦合,而是与比Employee类层次更低的抛出 EOFException 异常的代码耦合起来了。这样即破坏了封装性,也减低了代码的智力上的可管理性(intellectual manageablility)。

下面代码是一个抛出抽象层次一致的异常的类:

1
class
Emoyee{
2
...
3
public
TaxId
 GetTaxId()
throws
EmployeeDataNotAvailable{
4
...
5
}
6
...
7
}
GetTaxId() 里的异常处理代码可能只需要把一个 io_disk_not_ready(碰盘IO未就绪) 异常映射为 EmployeeDataNotAvailable(雇员数据不可用)异常就好了, 因为这样可以充分地保持接口的抽象性.

8. 最好是能把 Exception 都重新包装成自己系统定义的 Exception,这样可以减少上层函数需要声明的异常类型,否则有可能最高层函数后面要声明一长串 Exception, 同时也能确保异常的抽象层次与子程序接口的抽像导次是一致的.

例如:

读取一个JSON文件,然后解析JSON字符串,读取各个key的值,这时会碰到各种 exception,如 FileNotFoundException,JSONException,读取到的 value 进行类型转化时的 ParseException,这些 Exception 不能在 catch 到后直接重新抛出,应当把当前Context信息包装后,再抛出一个统一的更有意义的 Exception。

9. 一个比较好的项目实践总结.(整理自笨狐狸的架构)

假设现在开发一个 EmployeeManager 的系统, 则在异常管理模块, 先统一定义好两个相关的异常处理类, 代码如下:

01
//
 file EmployeeManagerException.java 统一的异常类
02
import
java.text.MessageFormat;
03
import
java.util.Locale;
04
import
java.util.ResourceBundle;
05
06
public
class
EmployeeManagerException
extends
RuntimeException
 {
07
private
final
String
 defaultKey =
"com.xxx.exception"
;
08
private
String
 exceptionKey;
09
private
Object[]
 args;
10
11
//
 国际化
12
private
ResourceBundle
 rb = ResourceBundle.getBundle(
"exceptionMessages"
,
13
Locale.getDefault());
14
15
public
EmployeeManagerException(ExceptionType
 type, Object... args) {
16
this
.exceptionKey
 = type.getExceptionKey();
17
this
.args
 = args;
18
}
19
20
public
String
 getMessage() {
21
if
((exceptionKey
 !=
null
)
 && !exceptionKey.equals(
""
))
 {
22
return
MessageFormat.format(rb.getString(exceptionKey),
 args);
23
}
24
25
return
MessageFormat.format(rb.getString(defaultKey),
 args);
26
}
27
}
01
//
 file ExceptionType.java异常枚举类
02
public
enum
ExceptionType
 {
03
COMMON_EXCEPTION(
"com.xxx.exception"
),
04
EMPLOYEE_DATA_NOT_***AILABLE(
"com.xxx.EmployeeDataNotAvailable"
);
05
06
private
ExceptionType(String
 exceptionKey) {
07
this
.exceptionKey
 = exceptionKey;
08
}
09
10
public
String
 getExceptionKey() {
11
return
exceptionKey;
12
}
13
14
private
String
 exceptionKey;
15
}
1
#
 file exceptionMessages_en.properties 定义需要进行格式化的 exception
2
com.sap.xxx.exception=Server
 side exception, the detail reason is {0}
3
com.xxx.EmployeeDataNotAvailable=Employee
 data not available, the detail reason is{0}
使用示例:

1
public
static
void
main(String[]
 args) {
2
try
{
3
//
 do something 
4
}
catch
(EOFException
 e){
5
throw
new
EmployeeManagerException(ExceptionType.EMPLOYEE_DATA_NOT_***AILABLE,
6
 
e.getMessage());
7
}
8
}
可以看到, 系统中所有低层次的异常都被重新包装成 EmployeeManagerException,并通过 ExceptionType指定其具体的异常类型, 然后在最后提取异常信息时, 根据 exceptionKey和 args[], 可以获得一个具有良好可读性, 可国际化的 Exception 信息. 在系统规模不大时, 可以采用这样的架构, 避免过多的异常类.

10. 对 9 架构的进一步思考.

这样的架构在需要添加新异常时, 就需要往 Exception 里新增一个枚举数据, 不符合 OCP 原则, 更好的办法应该是根据 EmployeeManagerException派生出各个不同的 EmployeeManagerException子类, 每一个子类代表一种异常, 具体代码如下:

01
//
 file EmployeeManagerException.java
02
import
java.io.EOFException;
03
import
java.text.MessageFormat;
04
import
java.util.Locale;
05
import
java.util.ResourceBundle;
06
07
public
abstract
class
EmployeeManagerException
extends
RuntimeException
 {
08
private
final
String
 defaultKey =
"com.xxx.exception"
;
09
private
String
 exceptionKey;
10
private
Object[]
 args;
11
12
//
 国际化
13
private
ResourceBundle
 rb = ResourceBundle.getBundle(
"exceptionMessages"
,
14
Locale.getDefault());
15
16
public
EmployeeManagerException(Object...
 args) {
17
this
.exceptionKey
 =
this
.getExceptionKey();
18
this
.args
 = args;
19
}
20
21
abstract
String
 getExceptionKey();
22
23
public
String
 getMessage() {
24
if
((exceptionKey
 !=
null
)
 && !exceptionKey.equals(
""
))
 {
25
return
MessageFormat.format(rb.getString(exceptionKey),
 args);
26
}
27
28
return
MessageFormat.format(rb.getString(defaultKey),
 args);
29
}
30
}
01
//
 file EmployeeDataNotAvailable.java
02
public
class
EmployeeDataNotAvailable
extends
EmployeeManagerException
 {
03
public
EmployeeDataNotAvailable(Object
 ... args){
04
super
(args);
05
}
06
 
07
@Override
08
String
 getExceptionKey() {
09
return
"com.xxx.EmployeeDataNotAvailable"
;
10
}
11
}
1
#
 file exceptionMessages_en.properties 定义需要进行格式化的 exception
2
com.sap.xxx.exception=Server
 side exception, the detail reason is {0}
3
com.xxx.EmployeeDataNotAvailable=Employee
 data not available, the detail reason is{0}
使用示例

1
public
static
void
main(String[]
 args) {
2
try
{
3
//
 throw new EOFException();
4
}
catch
(EOFException
 e){
5
throw
new
EmployeeDataNotAvailable(e.getMessage());
6
}
7
}
可以看到, 这个的架构功能是等同于9的, 而且能满足 OCP 原则,即新增一种异常时, 我们只需要从 EmployeeMnagerException 派生一个新的子类,重写 getExceptionKey 函数即可,然后在 properties 文件里添加新的一个 key/value 对即可。但是这样又会导致函数声明的 Exception 数过多的问题。

11. 异常声明具有“多态性”

如上例子中,如果有方法抛出了 EmployeeDataNotAvailable异常,那么它的函数声明的异常列表可以直接写成 throws EmployeeManagerException的,这与函数调用时的参数可以向上转型是一样的,所以 10 中可以使用统一 throws EmployeeManagerException来避免函数声明的 Exception 数过多的问题?

12. “checked exception” 转换为 "unchecked exception”(整理自《Java编程思想 第4版》,把“被检查的异常”转换为“不检查的异常”)

即把所有异常都转化为 RuntimeException,这样就无需在函数声明里列出异常名,每一个调用此函数的函数也不需要再写 try/catch 块,只需要在最外层捕获到这个 RuntimeException, 然后使用getCause() 函数把其实际的异常类型获取到即可。代码如下:

1
try
{
2
//..
 todo sometime useful
3
}
catch
(IDontKnowWhatToDoWIthThisCHeckedException
 e){
4
throw
new
RuntimeException(e);
5
}
个人不推荐这样的做法,把所有的异常完全不处理,一股脑丢给最上层处理是不负责任的做法,当系统稍具规模时,最上层也根本无法针对如此多的异常逐个进行处理。

环境: JDK1.6.0_30
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐