- 浏览: 565028 次
- 性别:
- 来自: 北京
文章分类
- 全部博客 (267)
- 随笔 (4)
- Spring (13)
- Java (61)
- HTTP (3)
- Windows (1)
- CI(Continuous Integration) (3)
- Dozer (1)
- Apache (11)
- DB (7)
- Architecture (41)
- Design Patterns (11)
- Test (5)
- Agile (1)
- ORM (3)
- PMP (2)
- ESB (2)
- Maven (5)
- IDE (1)
- Camel (1)
- Webservice (3)
- MySQL (6)
- CentOS (14)
- Linux (19)
- BI (3)
- RPC (2)
- Cluster (9)
- NoSQL (7)
- Oracle (25)
- Loadbalance (7)
- Web (5)
- tomcat (1)
- freemarker (1)
- 制造 (0)
最新评论
-
panamera:
如果设置了连接需要密码,Dynamic Broker-Clus ...
ActiveMQ 集群配置 -
panamera:
请问你的最后一种模式Broker-C节点是不是应该也要修改持久 ...
ActiveMQ 集群配置 -
maosheng:
longshao_feng 写道楼主使用 文件共享 模式的ma ...
ActiveMQ 集群配置 -
longshao_feng:
楼主使用 文件共享 模式的master-slave,produ ...
ActiveMQ 集群配置 -
tanglanwen:
感触很深,必定谨记!
少走弯路的十条忠告
异常,是Java中非常常用的功能,它可以简化代码,并且增强代码的安全性。本文将介绍一些异常高级知识,也是学习Java一来的一次总结。包括以下内内容:
异常的基础知识
异常特点
异常误用
如何正确地使用异常
异常的实现原理
关于异常
异常机制,是指程序不正常时的处理方式。具体来说,异常机制提供了程序退出的安全通道。当出现错误后,程序执行的流程发生改变,程序的控制权转移到异常处理器。
异常的一般性语法为:
try {
// 有可能抛出异常的代码
} catch (Exception e) {
// 异常处理
} finally {
// 无论是否捕获到异常都会执行的程序
}
Java异常体系
Throwable类是整个Java异常体系的超类,都有的异常类都是派生自这个类。包含Error和Exception两个直接子类。
Error表示程序在运行期间出现了十分严重、不可恢复的错误,在这种情况下应用程序只能中止运行,例如JAVA虚拟机出现错误。在程序中不用捕获Error类型的异常。一般情况下,在程序中也不应该抛出Error类型的异常。
Exception是应用层面上最顶层的异常类,包含RuntimeException(运行时异常)和 Checked Exception(受检异常)。
RuntimeException是一种Unchecked Exception,即表示编译器不会检查程序是否对RuntimeException作了处理,在程序中不必捕获RuntimException类型的异常,也不必在方法体声明抛出RuntimeException类。一般来说,RuntimeException发生的时候,表示程序中出现了编程错误,所以应该找出错误修改程序,而不是去捕获RuntimeException。常见的RuntimeException有NullPointException、ClassCastException、IllegalArgumentException、IndexOutOfBoundException等。
Checked Exception是相对于Unchecked Exception而言的,Java中并没有一个名为Checked Exception的类。它是在编程中使用最多的Exception,所有继承自Exception并且不是RuntimeException的异常都是Checked Exception。JAVA 语言规定必须对checked Exception作处理,编译器会对此作检查,要么在方法体中声明抛出checked Exception,要么使用catch语句捕获checked Exception进行处理,不然不能通过编译。常用的Checked Exception有IOException、ClassNotFoundException等。
异常的特点
通用特点
JVM捕获并处理未被应用程序捕获的异常
无论是受检异常(Checked Exception)还是运行时异常(Runtime Exception),如果异常没有被应用程序捕获,那么最终这个异常会交由JVM来进行处理,会明显出现下面两个结果:
1. 当前线程会停止运行,异常触发点后面的代码将得不到运行。
2. 异常栈信息会通过标准错误流输出。
/**
* 应用程序没有处理抛出的异常时,会交由JVM来处理这个异常。结果是:
* 1. 当前线程会停止运行,异常触发点后面的代码将得不到运行。
* 2. 异常栈信息会通过标准错误流输出。
*
* @author xialei
* @version 1.0 2016年5月18日下午9:53:54
*/
public class UncatchedException {
public static void main(String[] args) throws Exception {
throwException();
System.out.println("这一行不会被打印出来");
}
public static void throwException() throws Exception {
int i = 0;
if (i == 0) {
throw new Exception();
}
}
异常catch有顺序性
在catch异常时,如果有多个异常,那么是会有顺序要求的。子类型必须要在父类型之前进行catch,catch与分支逻辑是一致,如果父类型先被catch,那么后被catch的分支根本得不到运行机会。
/*
* 个人主页:http://hinylover.space
*
* Creation Date: 2016年4月7日 下午2:29:42
*/
package demo.blog.java.exception;
/**
* 在catch异常时,如果有多个异常,那么是会有顺序要求的。子类型必须要在父类型之前进行catch,
* catch与分支逻辑是一致,如果父类型先被catch,那么后被catch的分支根本得不到运行机会。
*
* @author xialei
* @version 1.0 2016年5月18日下午10:00:40
*/
public class ExceptionCatchOrder {
public void wrongCatchOrder() {
try {
Integer i = null;
int j = i;
} catch (Exception e) {
} catch (NullPointerException e) { // 编译不通过,eclipse提示“Unreachable catch block for NullPointerException. It is already handled by the catch block for Exception”
}
}
}
异常被吃掉
如果在finally中返回值,那么在程序中抛出的异常信息将会被吞噬掉。这是一个非常值得注意的问题,因为异常信息是非常重要的,在出现问题时,我们通常凭它来查找问题。如果编码不小心而导致异常被吞噬,排查起来是相当困难的,这将是一个大隐患。
/*
* 个人主页:http://hinylover.space
*
* Creation Date: 2016年4月7日 下午2:29:42
*/
package demo.blog.java.exception;
/**
* 如果在finally中返回值,那么在程序中抛出的异常信息将会被吞噬掉。
* @author xialei
* @version 1.0 2016年5月18日下午10:08:43
*/
public class FinallySwallowException {
public static void main(String[] args) throws Exception {
System.out.println(swallowException()); // 打印出2,而不是打印出异常栈
}
public static int swallowException() throws Exception {
try {
throw new Exception();
} finally {
return 2;
}
}
}
重写Exception的fillInStackTrace()方法
使用自定义异常时,可以重写fillInStackTrace()方法来控制Exception的异常栈信息。默认情况下,在程序抛出异常时,最终会通过调用private native Throwable fillInStackTrace(int dummy)这个本地方法来获取当前线程的堆栈信息,这是一个非常耗时的操作。如果我们仅仅需要用到异常的传播性质,而不关系异常的堆栈信息,那么完全可以通过重写fillInStackTrace()方法来实现。
/*
* 个人主页:http://hinylover.space
*
* Creation Date: 2016年4月7日 下午2:29:42
*/
package demo.blog.java.exception;
/**
* 重写Exception的fillInStackTrace()方法
*
* @author xialei
* @version 1.0 2016年5月18日下午10:18:57
*/
public class MyException extends Exception {
public MyException(String message) {
super(message);
}
/*
* 重写fillInStackTrace方法会使得这个自定义的异常不会收集线程的整个异常栈信息,会大大
* 提高减少异常开销。
*/
@Override
public synchronized Throwable fillInStackTrace() {
return this;
}
public static void main(String[] args) {
try {
throw new MyException("由于MyException重写了fillInStackTrace方法,那么它不会收集线程运行栈信息。");
} catch (MyException e) {
e.printStackTrace(); // 在控制台的打印结果为:demo.blog.java.exception.MyException: 由于MyException重写了fillInStackTrace方法,那么它不会收集线程运行栈信息。
}
}
}
受检异常(checked exception)
必须处理或者向上抛出
我们必须要对底层抛出来的受检异常进行处理,处理方式有try...catch...或者向上抛出(throws),否则程序无法通过编译。
package demo.blog.java.exception;
/**
* 必须对底层抛出的异常进行处理
* @author xialei
* @version 1.0 2016年5月18日下午10:42:53
*/
public class CheckedException {
public static void main(String[] args) {
throwException(); // 编译不通过,必须对底层抛出的异常进行处理
}
public static void throwException() throws Exception {
throw new Exception();
}
不能捕获未被抛出的受检异常
如果我们试图去捕获一个未被抛出的受检异常,程序将无法通过编译(Exception除外)。
/*
* 个人主页:http://hinylover.space
*
* Creation Date: 2016年5月18日 下午10:45:32
*/
package demo.blog.java.exception;
import java.io.IOException;
/**
* 不能捕获一个没有被抛出的受检异常(Exception除外)
* @author xialei
* @version 1.0 2016年5月18日下午10:45:32
*/
public class CantCatchUnthrowedException {
public void cantCatchUnthrowedException() {
try {
int i = 0;
} catch (IOException e) { // 编译不通过,eclipse提示:Unreachable catch block for IOException. This exception is never thrown from the try statement body
e.printStackTrace();
}
}
}
运行时异常(runtime exception)
运行时异常(runtime exception)与受检异常(checked exception)的最大区别是不强制对抛出的异常进行处理。所有的运行时异常都继承自RuntimeException这个类,别问为什么,Java是这么规定的。与受检异常类似的例子,如果抛出的是运行时异常,就算不捕获这个异常,程序也可以编译通过。
/*
* 个人主页:http://hinylover.space
*
* Creation Date: 2016年5月18日 下午11:02:57
*/
package demo.blog.java.exception;
/**
* 编译通过
* @author xialei
* @version 1.0 2016年5月18日下午11:02:57
*/
public class MyRuntimeException {
public void myRuntimeException() {
throw new RuntimeException(); // 可以正常编译
}
}
异常的使用
用受检异常还是运行时异常?
在使用异常时,笔者经常为使用何种异常而犯难,在实际使用过程中总结了一些小经验。
大概率发生时使用运行时异常
从概率上来说,如果这个异常发生的频率非常高,那么因为使用运行时异常,最典型的就是NullPointException。Java中调用每个对象的方法时,都有可能会发生NullPointException。如果这是一个受检异常,那么在每次调用对象方法要么try {} catch {},那么使用throws关键字向上抛出。无论哪种方式,代码无疑都会是非常丑陋的,那画面太“美”不敢看。如果代码里充斥着各种异常处理块,可读性将会大打折扣。
异常无法恢复时使用运行时异常
当异常发生时,如果开发者无法从异常状态恢复到正常状态,那么这种异常应该是运行时异常。如果使用受检异常,这除了加重开发者的负担之外,别无它用。当在调用其他方法时,如果方法抛出受检异常,那么笔者就会比较紧张。因为这意味着需要停止业务逻辑开发,然后开始思考如何处理这该死的异常。运行时异常通常是由于开发者编程不当所引起的,譬如空指针异常、除零异常等。如果开发者在开发过程中小心谨慎,考虑周全,就可能避免这种异常的发生。
可恢复时优先使用受检异常
如果我们能够从异常中恢复到正常状态,那么应该优先使用受检异常。为什么是优先而不是一定呢?因为从原理上来说,使用运行时异常也可以恢复到正常状态,而且使用运行时异常的代码无疑会比较干净整洁。而使用受检异常,明确地说明了调用方式时可能发生异常情况,强制开发者去处理这些异常情况常常会增强代码的健壮性。受检异常通常是由外部环境所引起的,譬如IOException等。
使用受检异常做流程控制
从Java语义上来说,应该是当程序层面真正发生异常状况时才应该使用异常(Exception),《Effect Java》一书中也建议只有真正的异常情况才使用异常。但我们有时也会利用异常来达到业务流程控制的目的。这样做主要有下面的好处:
简化代码逻辑。我们无需为多分枝业务流程编写各种if...else...语句来处理不同的情况。相反地,我们只需处理正常的业务流程即可,异常流程只需要通过异常向上抛出去即可,至于谁去处理这些异常,则不需要我们过多地关心。
可读性增强。如果一段代码中充斥着分枝逻辑,那么整个代码的可读性会非常差。在阅读代码时,很难理清楚代码的主干。说到底,主干代码才是我们重点关注的。如果使用异常进行流程控制,主干代码就清晰地显示在面前,两个字:舒服!
尽量集中处理异常
在各种有关代码重构的书本中,都会提到一个核心原则:一个方法应该仅做一件事情。如果一个方法中,既包含业务逻辑,又包含异常处理程序,那么实际上这个方法就做了两件事情。如果异常上层可以处理,那么就不应该在下层处理。在上层进行处理的好处是,可以对异常进行统一地处理。而至于将异常处理程序分散到代码的各个地方,导致维护起来十分困难。在进行异常处理时,应该优先考虑使用AOP(面向切面编程)技术,这样降低了核心业务逻辑与异常处理的耦合性。
自定义异常体系
在应用系统中应该要建立自己的异常体系,这样便于统一处理系统中出现的异常。笔者在开发过程中,通常会建立类似下图所示的系统体系。越靠近底层,越使用更加底层的、具体的异常。如果是其他系统中的异常(譬如Java自身的异常),也应该将其转化为自定义体系中的对应异常。
异常误用
e.printTrace()处理所有异常。
使用e.printTrace()来粗暴地处理所有异常是新手经常犯的毛病,笔者在初学时也是这么干的。为什么会出现这种情况呢?因为如果未处理的受检异常,代码将编译不通过,IDE(如eclipse)中会无情地打上各种红叉叉。这是IDE可以帮我们处理的情况,于是按照IDE的提示,try{}catch{}这段代码,其默认的异常处理就是调用e.printTrace()方法。初学者只顾着代码顺利通过编译,而完全没有考虑这样做存在的风险。这样做的风险如下:
错失正确的处理方法。相当一部分受检异常通过正确地处理是可以恢复正常,简单粗暴地使用e.printTrace()将错失恢复机会。
“丢失”异常信息。printTrace()方法是Throwable类中的一个方法,它的作用是将异常栈信息打印到标准错误流中。如果Web项目,会将异常信息打印到容器的日志文件中;如果是普通项目,通常会将标准输出和标准错误重定向到dev/null(空设备)中。无论是何种情况,都有可能导致异常信息“丢失”(web容器中的日志其实不代表丢失了,但是我们通常不会利用容器级别的日志排错),给排错带来很大的麻烦。
全部使用运行时异常
为了“偷懒”,无论什么情况都使用运行时异常,这样就可以不用费劲处理异常了,轻轻松松。但是,现在没事不代表以后不出事,这无疑为代码埋下了隐患。如果在调用一个方法时,该方法并没有显示地抛出异常,也没有在javadoc中强调,我们就不会知道调用这段代码可能发生的异常情况,那些可能出现的异常情况对我们来说是透明的。
总是catch Exception对象
在捕获和处理异常时,不管3721,一股脑地catch Exception对象。没错,这样就可以一次性处理所有异常情况。但是,所有地异常都使用相同的处理程序真的对吗?在这样做之前应该先要打个大大的问号。这样做的后果是我们会无形中忽略那些重要的异常。
正确使用异常
正确地处理异常。针对不用的异常采取合适的、正确的异常处理方式,不要遇到任何异常都printTrace()或者打印一个日志。
catch时指定具体的异常。不要一股脑地catch Exception,具体的异常应该单独catch住,越具体的异常越早catch。
涉及到资源时,需要finally。如果涉及到资源的关闭时,应该将关闭资源的代码写在finally代码块内。
最小化try{ } catch{ }范围。try的范围应该尽量小,最好就是try住抛出异常的那个方法即可。
异常的实现原理(字节码级别)
从字节码层面上来分析一下Java异常的实现原理,编写如下所示的源代码,使用javac命令进行编译,然后使用javap命令查看编译后的字节码细节内容。
public class ExceptionClassCode {
public int demo() {
int x;
try {
x = 1;
return x;
} catch (Exception e) {
x = 2;
return x;
} finally {
x = 3
}
}
}
public int demo();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=1, locals=5, args_size=1
0: iconst_1 // 生成整数1
1: istore_1 // 将生成的整数1赋予第1号局部变量(x=1)
2: iload_1 // 将x(=1)的值入栈
3: istore_2 // 将栈顶的值(=1)赋予第2号变量(returnValue)
4: iconst_3 // 生成整数3
5: istore_1 // x=3
6: iload_2 // returnValue=当前栈顶值(=1)
7: ireturn // 返回returnValue(=1)
8: astore_2 // 将Exception对象引用值赋予第2号局部变量
9: iconst_2 // 生成整数2
10: istore_1 // x=2
11: iload_1 // x(=2)压入栈顶
12: istore_3 // 将栈顶的值(=2)赋予第3号变量(returnValue)
13: iconst_3 // 生成整数3
14: istore_1 // x=3
15: iload_3 // returnValue(=2)压入栈顶
16: ireturn // 返回returnValue(=2)
17: astore 4 // 将异常信息保存到第4号局部变量
19: iconst_3 // 生成整数3
20: istore_1 // x=3
21: aload 4 // 将异常引用值压入栈
23: athrow // 抛出栈顶所引用的异常
Exception table:
from to target type
0 4 8 Class java/lang/Exception # 如果0~4行字节码(try代码块)中出现Exception及其子类异常,则执行第8行(catch代码行)
0 4 17 any # 无论0~4行字节码(try代码块)是否抛出异常,都执行第17行(finally代码行)
8 13 17 any # 无论8~13行字节码(catch代码块)是否抛出异常,都执行第17行(finally代码行)
17 19 17 any 123456789101112131415161718192021222324252627282930313233
看到字节码中有一个Exception table(异常表)区域,这个就是与异常相关的字节码内容。它表示在from到to所指示的字节码行中,如果抛出type所对应的异常(及其子类),那么就跳到target指定的字节码行开始执行。
异常的基础知识
异常特点
异常误用
如何正确地使用异常
异常的实现原理
关于异常
异常机制,是指程序不正常时的处理方式。具体来说,异常机制提供了程序退出的安全通道。当出现错误后,程序执行的流程发生改变,程序的控制权转移到异常处理器。
异常的一般性语法为:
try {
// 有可能抛出异常的代码
} catch (Exception e) {
// 异常处理
} finally {
// 无论是否捕获到异常都会执行的程序
}
Java异常体系
Throwable类是整个Java异常体系的超类,都有的异常类都是派生自这个类。包含Error和Exception两个直接子类。
Error表示程序在运行期间出现了十分严重、不可恢复的错误,在这种情况下应用程序只能中止运行,例如JAVA虚拟机出现错误。在程序中不用捕获Error类型的异常。一般情况下,在程序中也不应该抛出Error类型的异常。
Exception是应用层面上最顶层的异常类,包含RuntimeException(运行时异常)和 Checked Exception(受检异常)。
RuntimeException是一种Unchecked Exception,即表示编译器不会检查程序是否对RuntimeException作了处理,在程序中不必捕获RuntimException类型的异常,也不必在方法体声明抛出RuntimeException类。一般来说,RuntimeException发生的时候,表示程序中出现了编程错误,所以应该找出错误修改程序,而不是去捕获RuntimeException。常见的RuntimeException有NullPointException、ClassCastException、IllegalArgumentException、IndexOutOfBoundException等。
Checked Exception是相对于Unchecked Exception而言的,Java中并没有一个名为Checked Exception的类。它是在编程中使用最多的Exception,所有继承自Exception并且不是RuntimeException的异常都是Checked Exception。JAVA 语言规定必须对checked Exception作处理,编译器会对此作检查,要么在方法体中声明抛出checked Exception,要么使用catch语句捕获checked Exception进行处理,不然不能通过编译。常用的Checked Exception有IOException、ClassNotFoundException等。
异常的特点
通用特点
JVM捕获并处理未被应用程序捕获的异常
无论是受检异常(Checked Exception)还是运行时异常(Runtime Exception),如果异常没有被应用程序捕获,那么最终这个异常会交由JVM来进行处理,会明显出现下面两个结果:
1. 当前线程会停止运行,异常触发点后面的代码将得不到运行。
2. 异常栈信息会通过标准错误流输出。
/**
* 应用程序没有处理抛出的异常时,会交由JVM来处理这个异常。结果是:
* 1. 当前线程会停止运行,异常触发点后面的代码将得不到运行。
* 2. 异常栈信息会通过标准错误流输出。
*
* @author xialei
* @version 1.0 2016年5月18日下午9:53:54
*/
public class UncatchedException {
public static void main(String[] args) throws Exception {
throwException();
System.out.println("这一行不会被打印出来");
}
public static void throwException() throws Exception {
int i = 0;
if (i == 0) {
throw new Exception();
}
}
异常catch有顺序性
在catch异常时,如果有多个异常,那么是会有顺序要求的。子类型必须要在父类型之前进行catch,catch与分支逻辑是一致,如果父类型先被catch,那么后被catch的分支根本得不到运行机会。
/*
* 个人主页:http://hinylover.space
*
* Creation Date: 2016年4月7日 下午2:29:42
*/
package demo.blog.java.exception;
/**
* 在catch异常时,如果有多个异常,那么是会有顺序要求的。子类型必须要在父类型之前进行catch,
* catch与分支逻辑是一致,如果父类型先被catch,那么后被catch的分支根本得不到运行机会。
*
* @author xialei
* @version 1.0 2016年5月18日下午10:00:40
*/
public class ExceptionCatchOrder {
public void wrongCatchOrder() {
try {
Integer i = null;
int j = i;
} catch (Exception e) {
} catch (NullPointerException e) { // 编译不通过,eclipse提示“Unreachable catch block for NullPointerException. It is already handled by the catch block for Exception”
}
}
}
异常被吃掉
如果在finally中返回值,那么在程序中抛出的异常信息将会被吞噬掉。这是一个非常值得注意的问题,因为异常信息是非常重要的,在出现问题时,我们通常凭它来查找问题。如果编码不小心而导致异常被吞噬,排查起来是相当困难的,这将是一个大隐患。
/*
* 个人主页:http://hinylover.space
*
* Creation Date: 2016年4月7日 下午2:29:42
*/
package demo.blog.java.exception;
/**
* 如果在finally中返回值,那么在程序中抛出的异常信息将会被吞噬掉。
* @author xialei
* @version 1.0 2016年5月18日下午10:08:43
*/
public class FinallySwallowException {
public static void main(String[] args) throws Exception {
System.out.println(swallowException()); // 打印出2,而不是打印出异常栈
}
public static int swallowException() throws Exception {
try {
throw new Exception();
} finally {
return 2;
}
}
}
重写Exception的fillInStackTrace()方法
使用自定义异常时,可以重写fillInStackTrace()方法来控制Exception的异常栈信息。默认情况下,在程序抛出异常时,最终会通过调用private native Throwable fillInStackTrace(int dummy)这个本地方法来获取当前线程的堆栈信息,这是一个非常耗时的操作。如果我们仅仅需要用到异常的传播性质,而不关系异常的堆栈信息,那么完全可以通过重写fillInStackTrace()方法来实现。
/*
* 个人主页:http://hinylover.space
*
* Creation Date: 2016年4月7日 下午2:29:42
*/
package demo.blog.java.exception;
/**
* 重写Exception的fillInStackTrace()方法
*
* @author xialei
* @version 1.0 2016年5月18日下午10:18:57
*/
public class MyException extends Exception {
public MyException(String message) {
super(message);
}
/*
* 重写fillInStackTrace方法会使得这个自定义的异常不会收集线程的整个异常栈信息,会大大
* 提高减少异常开销。
*/
@Override
public synchronized Throwable fillInStackTrace() {
return this;
}
public static void main(String[] args) {
try {
throw new MyException("由于MyException重写了fillInStackTrace方法,那么它不会收集线程运行栈信息。");
} catch (MyException e) {
e.printStackTrace(); // 在控制台的打印结果为:demo.blog.java.exception.MyException: 由于MyException重写了fillInStackTrace方法,那么它不会收集线程运行栈信息。
}
}
}
受检异常(checked exception)
必须处理或者向上抛出
我们必须要对底层抛出来的受检异常进行处理,处理方式有try...catch...或者向上抛出(throws),否则程序无法通过编译。
package demo.blog.java.exception;
/**
* 必须对底层抛出的异常进行处理
* @author xialei
* @version 1.0 2016年5月18日下午10:42:53
*/
public class CheckedException {
public static void main(String[] args) {
throwException(); // 编译不通过,必须对底层抛出的异常进行处理
}
public static void throwException() throws Exception {
throw new Exception();
}
不能捕获未被抛出的受检异常
如果我们试图去捕获一个未被抛出的受检异常,程序将无法通过编译(Exception除外)。
/*
* 个人主页:http://hinylover.space
*
* Creation Date: 2016年5月18日 下午10:45:32
*/
package demo.blog.java.exception;
import java.io.IOException;
/**
* 不能捕获一个没有被抛出的受检异常(Exception除外)
* @author xialei
* @version 1.0 2016年5月18日下午10:45:32
*/
public class CantCatchUnthrowedException {
public void cantCatchUnthrowedException() {
try {
int i = 0;
} catch (IOException e) { // 编译不通过,eclipse提示:Unreachable catch block for IOException. This exception is never thrown from the try statement body
e.printStackTrace();
}
}
}
运行时异常(runtime exception)
运行时异常(runtime exception)与受检异常(checked exception)的最大区别是不强制对抛出的异常进行处理。所有的运行时异常都继承自RuntimeException这个类,别问为什么,Java是这么规定的。与受检异常类似的例子,如果抛出的是运行时异常,就算不捕获这个异常,程序也可以编译通过。
/*
* 个人主页:http://hinylover.space
*
* Creation Date: 2016年5月18日 下午11:02:57
*/
package demo.blog.java.exception;
/**
* 编译通过
* @author xialei
* @version 1.0 2016年5月18日下午11:02:57
*/
public class MyRuntimeException {
public void myRuntimeException() {
throw new RuntimeException(); // 可以正常编译
}
}
异常的使用
用受检异常还是运行时异常?
在使用异常时,笔者经常为使用何种异常而犯难,在实际使用过程中总结了一些小经验。
大概率发生时使用运行时异常
从概率上来说,如果这个异常发生的频率非常高,那么因为使用运行时异常,最典型的就是NullPointException。Java中调用每个对象的方法时,都有可能会发生NullPointException。如果这是一个受检异常,那么在每次调用对象方法要么try {} catch {},那么使用throws关键字向上抛出。无论哪种方式,代码无疑都会是非常丑陋的,那画面太“美”不敢看。如果代码里充斥着各种异常处理块,可读性将会大打折扣。
异常无法恢复时使用运行时异常
当异常发生时,如果开发者无法从异常状态恢复到正常状态,那么这种异常应该是运行时异常。如果使用受检异常,这除了加重开发者的负担之外,别无它用。当在调用其他方法时,如果方法抛出受检异常,那么笔者就会比较紧张。因为这意味着需要停止业务逻辑开发,然后开始思考如何处理这该死的异常。运行时异常通常是由于开发者编程不当所引起的,譬如空指针异常、除零异常等。如果开发者在开发过程中小心谨慎,考虑周全,就可能避免这种异常的发生。
可恢复时优先使用受检异常
如果我们能够从异常中恢复到正常状态,那么应该优先使用受检异常。为什么是优先而不是一定呢?因为从原理上来说,使用运行时异常也可以恢复到正常状态,而且使用运行时异常的代码无疑会比较干净整洁。而使用受检异常,明确地说明了调用方式时可能发生异常情况,强制开发者去处理这些异常情况常常会增强代码的健壮性。受检异常通常是由外部环境所引起的,譬如IOException等。
使用受检异常做流程控制
从Java语义上来说,应该是当程序层面真正发生异常状况时才应该使用异常(Exception),《Effect Java》一书中也建议只有真正的异常情况才使用异常。但我们有时也会利用异常来达到业务流程控制的目的。这样做主要有下面的好处:
简化代码逻辑。我们无需为多分枝业务流程编写各种if...else...语句来处理不同的情况。相反地,我们只需处理正常的业务流程即可,异常流程只需要通过异常向上抛出去即可,至于谁去处理这些异常,则不需要我们过多地关心。
可读性增强。如果一段代码中充斥着分枝逻辑,那么整个代码的可读性会非常差。在阅读代码时,很难理清楚代码的主干。说到底,主干代码才是我们重点关注的。如果使用异常进行流程控制,主干代码就清晰地显示在面前,两个字:舒服!
尽量集中处理异常
在各种有关代码重构的书本中,都会提到一个核心原则:一个方法应该仅做一件事情。如果一个方法中,既包含业务逻辑,又包含异常处理程序,那么实际上这个方法就做了两件事情。如果异常上层可以处理,那么就不应该在下层处理。在上层进行处理的好处是,可以对异常进行统一地处理。而至于将异常处理程序分散到代码的各个地方,导致维护起来十分困难。在进行异常处理时,应该优先考虑使用AOP(面向切面编程)技术,这样降低了核心业务逻辑与异常处理的耦合性。
自定义异常体系
在应用系统中应该要建立自己的异常体系,这样便于统一处理系统中出现的异常。笔者在开发过程中,通常会建立类似下图所示的系统体系。越靠近底层,越使用更加底层的、具体的异常。如果是其他系统中的异常(譬如Java自身的异常),也应该将其转化为自定义体系中的对应异常。
异常误用
e.printTrace()处理所有异常。
使用e.printTrace()来粗暴地处理所有异常是新手经常犯的毛病,笔者在初学时也是这么干的。为什么会出现这种情况呢?因为如果未处理的受检异常,代码将编译不通过,IDE(如eclipse)中会无情地打上各种红叉叉。这是IDE可以帮我们处理的情况,于是按照IDE的提示,try{}catch{}这段代码,其默认的异常处理就是调用e.printTrace()方法。初学者只顾着代码顺利通过编译,而完全没有考虑这样做存在的风险。这样做的风险如下:
错失正确的处理方法。相当一部分受检异常通过正确地处理是可以恢复正常,简单粗暴地使用e.printTrace()将错失恢复机会。
“丢失”异常信息。printTrace()方法是Throwable类中的一个方法,它的作用是将异常栈信息打印到标准错误流中。如果Web项目,会将异常信息打印到容器的日志文件中;如果是普通项目,通常会将标准输出和标准错误重定向到dev/null(空设备)中。无论是何种情况,都有可能导致异常信息“丢失”(web容器中的日志其实不代表丢失了,但是我们通常不会利用容器级别的日志排错),给排错带来很大的麻烦。
全部使用运行时异常
为了“偷懒”,无论什么情况都使用运行时异常,这样就可以不用费劲处理异常了,轻轻松松。但是,现在没事不代表以后不出事,这无疑为代码埋下了隐患。如果在调用一个方法时,该方法并没有显示地抛出异常,也没有在javadoc中强调,我们就不会知道调用这段代码可能发生的异常情况,那些可能出现的异常情况对我们来说是透明的。
总是catch Exception对象
在捕获和处理异常时,不管3721,一股脑地catch Exception对象。没错,这样就可以一次性处理所有异常情况。但是,所有地异常都使用相同的处理程序真的对吗?在这样做之前应该先要打个大大的问号。这样做的后果是我们会无形中忽略那些重要的异常。
正确使用异常
正确地处理异常。针对不用的异常采取合适的、正确的异常处理方式,不要遇到任何异常都printTrace()或者打印一个日志。
catch时指定具体的异常。不要一股脑地catch Exception,具体的异常应该单独catch住,越具体的异常越早catch。
涉及到资源时,需要finally。如果涉及到资源的关闭时,应该将关闭资源的代码写在finally代码块内。
最小化try{ } catch{ }范围。try的范围应该尽量小,最好就是try住抛出异常的那个方法即可。
异常的实现原理(字节码级别)
从字节码层面上来分析一下Java异常的实现原理,编写如下所示的源代码,使用javac命令进行编译,然后使用javap命令查看编译后的字节码细节内容。
public class ExceptionClassCode {
public int demo() {
int x;
try {
x = 1;
return x;
} catch (Exception e) {
x = 2;
return x;
} finally {
x = 3
}
}
}
public int demo();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=1, locals=5, args_size=1
0: iconst_1 // 生成整数1
1: istore_1 // 将生成的整数1赋予第1号局部变量(x=1)
2: iload_1 // 将x(=1)的值入栈
3: istore_2 // 将栈顶的值(=1)赋予第2号变量(returnValue)
4: iconst_3 // 生成整数3
5: istore_1 // x=3
6: iload_2 // returnValue=当前栈顶值(=1)
7: ireturn // 返回returnValue(=1)
8: astore_2 // 将Exception对象引用值赋予第2号局部变量
9: iconst_2 // 生成整数2
10: istore_1 // x=2
11: iload_1 // x(=2)压入栈顶
12: istore_3 // 将栈顶的值(=2)赋予第3号变量(returnValue)
13: iconst_3 // 生成整数3
14: istore_1 // x=3
15: iload_3 // returnValue(=2)压入栈顶
16: ireturn // 返回returnValue(=2)
17: astore 4 // 将异常信息保存到第4号局部变量
19: iconst_3 // 生成整数3
20: istore_1 // x=3
21: aload 4 // 将异常引用值压入栈
23: athrow // 抛出栈顶所引用的异常
Exception table:
from to target type
0 4 8 Class java/lang/Exception # 如果0~4行字节码(try代码块)中出现Exception及其子类异常,则执行第8行(catch代码行)
0 4 17 any # 无论0~4行字节码(try代码块)是否抛出异常,都执行第17行(finally代码行)
8 13 17 any # 无论8~13行字节码(catch代码块)是否抛出异常,都执行第17行(finally代码行)
17 19 17 any 123456789101112131415161718192021222324252627282930313233
看到字节码中有一个Exception table(异常表)区域,这个就是与异常相关的字节码内容。它表示在from到to所指示的字节码行中,如果抛出type所对应的异常(及其子类),那么就跳到target指定的字节码行开始执行。
发表评论
-
java 类的加载 以及 ClassLoader
2020-04-16 09:43 445Class Loader 类加载器: 类加载器负责加载 ... -
Stack 的实现原理深入剖析
2020-04-06 13:26 484Stack 介绍: Stack是栈。 ... -
Vector 的实现原理深入剖析
2020-04-06 13:17 362Vector介绍: Vector 是矢量队列,它是JDK1. ... -
JDK 分析工具
2020-04-05 17:30 361常用分析工具: jps:显示指定系统中所有的HotSpot虚 ... -
二叉树的深度优先遍历和广度优先遍历
2020-03-10 09:33 593概述: 1、深度优先遍历(Depth-First-Sear ... -
Hashtable 的实现原理深入剖析
2020-02-18 20:59 529一、Hashtable的基本方法: 1、定义: HashT ... -
jdk 1.8 新特性
2020-02-17 13:43 3591、default关键字 ... -
Java IO 架构
2019-11-11 16:39 349主要两类: 磁盘I/O 网络I/O 基于字节 ... -
Java 数据结构与算法
2019-04-03 10:25 514程序=数据结构+算法 ... -
Java并发问题--乐观锁与悲观锁以及乐观锁的一种实现方式-CAS
2018-08-17 09:47 1466首先介绍一些乐观锁与 ... -
Java 高性能编程注意事项
2016-11-17 09:55 6441. 尽量在合适的场合使用单例 使用单例可以减轻加载的负担, ... -
Netty 解析
2017-03-07 13:47 1218Linux网络IO模型: Linux ... -
2016年Java 面试题总结
2016-01-18 13:34 54790多线程、并发及线程的基础问题: 1)Java 中能创建 vo ... -
java 内存模型
2015-12-29 13:44 815JAVA内存模型: Java内存 ... -
JVM 深入剖析
2015-12-29 12:51 1087JVM是JAVA虚拟机(JAVA Virtual Machin ... -
Java 并发编程_Synchronized
2015-12-16 12:42 868硬件的效率和一致性: 由于计算机的运算速度和它的存储和通讯子 ... -
Java 并发编程_Volatile
2015-12-15 13:42 617术语定义: 共享变量:在多个线程之间能够被共享的变量被称为共 ... -
Java 并发编程_ConcurrentLinkedQueue
2015-12-15 13:32 904ConcurrentLinkedQueue 的分析和使用: ... -
Java 并发编程_ConcurrentHashMap
2015-11-10 11:30 824ConcurrentHashMap 的分析和 ... -
JVM 垃圾回收原理
2015-10-29 13:38 488基本回收算法: 1.引用 ...
相关推荐
在Java编程语言中,异常处理是一项关键特性,用于处理程序运行时可能出现的错误或异常情况。当程序遇到不可预见的问题时,它会抛出一个异常。Java提供了丰富的内置异常类,但有时候,我们可能需要根据特定的需求创建...
JAVA语言的异常可以分为两大类:Checked Exception和Unchecked Exception。Checked Exception是编译器强制捕获的异常,而Unchecked Exception是运行时抛出的异常。常见的Checked Exception包括IOException、...
- **异常的表示**:异常主要通过`java.lang.Exception`及其子类的实例来表示。另一个重要的异常类型是`java.io.IOException`,用于处理输入输出流中的异常。 - **异常处理流程**:当程序中发生异常时,异常会被“抛...
在Java编程语言中,异常处理是一项关键特性,用于在程序执行过程中捕获并处理错误或不正常的情况。异常是程序运行时出现的问题,通常会导致程序中断。Java提供了丰富的异常类来表示不同类型的错误,其中就包括与时间...
### Java语言中的两种异常:深入理解与应用 Java作为一门广泛应用的编程语言,其异常处理机制是其设计中的一大亮点,旨在帮助开发者更好地控制和处理程序运行时可能遇到的错误。在Java中,异常主要分为两大类:`...
Java 异常机制是 Java 语言中的一种错误处理机制,它可以帮助开发者捕获和处理程序运行时的错误和异常。Java 异常机制包括 Error、Exception、检查异常、运行时异常、异常处理和 throws 关键字等几个方面。 Error ...
* 异常类型:Java语言提供了多种异常类型,包括checked exception和unchecked exception。 * try-catch语句:try-catch语句是Java语言中异常处理的基本结构。 * throw语句:throw语句用于抛出异常。 * finally语句:...
Java 异常的对象来自两个来源:一是 Java 语言本身定义的一些基本异常类型,二是用户通过继承 Exception 类或者其子类自己定义的异常。异常的对象可以是 Java 运行时环境自动抛出的系统生成的异常,也可以是程序员...
Java 异常处理是 Java 编程语言中的一种重要机制,用于处理程序在运行时可能出现的错误或异常情况。下面是关于 Java 异常处理的习题和知识点总结: 一、Java 异常处理关键字 * Java 中用来抛出异常的关键字是 `...
Java编程语言中,异常处理是程序健壮性的重要组成部分。异常是程序运行时出现的错误,它们被封装成对象,并通过异常类来表示。以下是一些在Java中常见的异常类及其含义: 1. `ArithmeticException`:当计算中出现...
在Java编程语言中,当你尝试执行一个除以零的操作时,会抛出一个`ArithmeticException`。这个异常属于运行时异常,表明程序试图进行一个非法或无意义的数学运算,比如整数除以零。在Java中,任何类型的数字(无论是...
Java 异常处理是 Java 语言中的一个关键组件,用于处理程序运行过程中的错误和异常。在 Java 中,异常处理框架是 Java 语言健壮性的一个重要体现。本文将从 Java 异常的基本概念和语法开始,讲述 Java 异常处理的...
Java异常Exception类及其子类是Java语言中异常处理的核心部分。异常是指程序在执行过程中出现的非正常情况,例如除数为零、数组索引越界、类型转换错误等。Java语言所有的异常都可以用一个类来表示,不同类型的异常...
Java语言异常处理技术是编程过程中不可或缺的一部分,它旨在确保程序在遇到错误或异常情况时能够优雅地处理问题,而不是突然崩溃。Java通过一套强大的异常处理机制来实现这一目标,包括异常类、捕获和抛出异常、...
异常处理是Java编程语言中的一个重要机制,用于处理程序执行过程中可能出现的错误。通过合理地管理异常,开发者能够使程序更加健壮,提高系统的稳定性和可用性。 #### 二、异常的概念 在Java中,异常主要分为两大...
Java 异常处理是 Java 语言中的一种错误处理机制,用于捕获和处理程序执行过程中出现的异常情况。异常是指程序执行过程中出现的非正常情况,例如除零错误、数组下标越界等。 实验目的 本实验的目的是让学生熟悉 ...