前言:对于异常情况,例如,可能造成程序崩溃的错误输入,Java是通过捕获机制来处理异常错误。当程序出错时,我们不可能总是及时和用户沟通,所以希望记录出现的问题,以备日后进行分析。
如何处理错误
当一个用户在运行程序期间,由于程序的错误或一些外部环境的影响造成用户数据的丢失,用户就有可能不再使用这个程序了。为了避免这类事情发生,我们应该注意以下几点。
- 向用户通知错误的原因
- 保存所有的工作结果(日志)
- 允许用户以妥善的形式退出程序
异常分类
所有Java异常对象都是由Throwable继承而来。如果Java内置的异常类不满足我们的需求,我们可以创建自己的异常类。下面是我画的Java异常结构的一个简化示意图。
Error | 是描述Java运行时系统内部错误和资源耗尽错误。应用程序不应该抛出这种类型的对象。如果出现了这样内部错误,除了告知客户,并尽力使用程序安全地终止之外,别无他法,不过这样的情况很少出现。 |
Exception | 是描述Java应用程序抛出和处理的非严重型错误,程序错误导致的异常就属于RuntimeException;而程序本身没有问题,但由于I/O错误这类问题导致的异常属于其他异常。 |
派生于RuntimeException的异常包含下面几种情况:
- 错误的类型转换
- 数组下标越界
- 空指针异常
不是派生于RuntimeException的异常(其他异常):
- 在文件尾部后面读取数据
- 打开一个不存在的文件
- 根据给定的字符串查找Class对象,而这个字符串表示的类不存在
如果存在RuntimeException异常,那么就一定是你的问题这是一条非常有道理的规则,不知道大家认不认可,我们应该在变量使用前检测是否为null可以防止出现NullPointerException(空指针异常);通过检测数组下标是否越界可以避免ArrayIndexOutOfBoundsException异常。
抛出异常
当我们需要在一个方法中抛出一个异常时,我们使用throw后加某异常类的实例,程序会在此向客户端程序(调用这段代码的程序)抛出对应异常并在此退出(相当于return)。另外需要注意的是,我们必须在定义该方法的时候指明异常类型,比如下面这段代码会抛出testException异常。
public void demo() throws testException,AException,BException{
throw new testException();
}
不同的异常类之间用逗号隔开即可,在这种情况下我们不必须throw每个异常类的实例(),但是客户端代码必须要catch到每个异常类:
public class MyException {
public static void main(String[] args){
MyException e = new MyException();
try {
e.demo();
} catch (testException e1) {
e1.printStackTrace();
} catch (BException e1) {
e1.printStackTrace();
} catch (AException e1) {
e1.printStackTrace();
}
}
public void a() throws testException,AException,BException{
throw new testException();
}
}
class testException extends Exception {};
class AException extends Exception{};
class BException extends Exception{};
捕获异常
1.捕获单个异常
try {
//code more code
}catch (Exception e){
//handler for this type
}
2.捕获多个异常
注:捕获多个异常不仅让你代码看起来更简单,还会更加高效。
try {
...
}catch (FileNotFoundException e){
...
}catch (UnknownHostException e){
...
}catch (IOException e){
...
}
异常对象可能包含与异常本身有关的信息。要想获取对象更多的信息,可以尝试使用e.getMessage(),得到详细的错误信息(如果有的话),或者使用e.getClass().getName()得到异常对象的实际类型。
3.再次抛出异常与异常链
在catch子句中可以抛出一个异常,这样做是为了改变异常的类型。如果我们开发了一个供其他程序使用的子系统,那么表示子系统故障的异常类型可能会产生很多种解释。ServletException就是如此,执行servlet代码可能不想知道发生错误的细节,但希望明确知道servlet是否有问题。
try {
//连接database
}catch (SQLException e){
throw new ServeletException("database error:"+e.getMessage());//throw抛异常
}
这里,ServeletException用带有异常信息文本的构造器来构造。不过可以有一种更好的处理方法,并且将原始异常设置为新异常的原因:
try {
//连接database
}catch (SQLException e){
Throwable se = new ServeletException("database error");
se.initCause(e);
throw se;
}
当捕获到异常时,就可以用下面这条语句重新得到原始异常:
Throwable e = se.getCause();
强烈建议使用这种包装技术。可以让用户抛出子系统的高级异常,而不会丢失原始异常的细节。
小提示:如果在一个方法中发生异常,而又不允许抛出它,那么包装技术就十分重要。我们可以捕获这个异常,并将它包装成一个运行时异常。比如:有时候你可能只想记录一个异常,再将它重新抛出,而不做任何的改变,代码示例如下:
try {
//连接database
}catch (Exception e){
logger.log(level,message,e);//日志
throw e;
}
4.finally子句
不管是否有异常被捕获,finally子句中的代码总是会被执行。当finally包含return时,将会出现意想不到的结果,假设从try中退出return。在方法返回前,finally中的内容将会被执行。如果finally中也有return,这个返回值会覆盖原始的返回值,例子如下:
public static int(int n){
try{
int r = n*n;
return r;
}
finally{
if(n==2) return 0;
}
}
如果在finally中关闭资源,最好加上处理,防止异常,这是最传统的做法。下面第五点将给大家介绍jdk7之后资源关闭的方式。
finally{
try{
in.close();
}catch(Exception e){
throw e;
}
}
5.try-with-resource(带资源的try)
如果打开了外部资源(文件、数据库连接、网络连接等),我们必须在这些外部资源使用完毕后,手动关闭它们。因为外部资源不由JVM管理,无法享用JVM的垃圾回收机制,如果我们不在编程时确保在正确的时机关闭外部资源,就会导致外部资源泄露,紧接着就会出现文件被异常占用,数据库连接过多导致连接池溢出等诸多很严重的问题。
当try退出时,会自动调用close方法,去读取单词中的所有单词,示例如下:
try(Scanner in = new Scanner(New FileInputStream("/user/share/dict/words")),"UTF-8"){
while(in.hasNext()){
System.out.println(in.next());
}
}
如果try抛出一个异常,而且close方法也抛出一个异常,这样就会带来一个难题。到资源的try可以很好地处理。原来的close方法抛出的异常被抑制。这些异常将会自动捕获,并由addSuperssed方法增加到原来的异常。如果对这些异常感兴趣,可以调用getSupperssed方法,它会得到从close抛出并抑制的异常列表。
自定义异常
有时候Java内置异常不满足于我们的需求,我们就需要用到自定义异常。比如:系统中有些错误是符合Java语法的,但不符合我们项目的业务逻辑;企业项目是分模块或者分功能开发的 ,使用自定义异常类就统一了对外异常展示的方式等。
所有异常都必须是 Throwable 的子类。 |
如果希望写一个检查性异常类,则需要继承 Exception 类。 |
如果你想写一个运行时异常类,那么需要继承 RuntimeException 类。 |
创建一个检查性异常类
public class DatamartAssertException extends Exception {
private static final long serialVersionUID = 7358625131054183395L;
public DatamartAssertException() {
}
public DatamartAssertException(String message) {
super(message);
}
}
抛出和使用自定义异常
public static String HttpPost() throws DatamartAssertException{
//创建httpClient实例
CloseableHttpClient httpClient = HttpClients.createDefault();
//地址url
String url = "";
//创建HttpPost实例
HttpPost method = new HttpPost(url);
//设置请求读取超时时间 60秒
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(60000).setConnectionRequestTimeout(60000)
.setSocketTimeout(60000).build();
method.setConfig(requestConfig);
//执行请求
HttpResponse result = httpClient.execute(method);
/**请求发送成功,并得到响应**/
if (result.getStatusLine().getStatusCode() == 200) {
String str = "";
/**读取服务器返回过来的json字符串数据**/
str = EntityUtils.toString(result.getEntity());
}
}catch(ConnectTimeoutException e){
// 捕获超时异常 并反馈给调用者
//e.printStackTrace();
throw new DatamartAssertException("请求超时!");
}catch(ConnectException e) {
throw new DatamartAssertException("请求超时!");
}catch (Exception e){
e.printStackTrace();
return null;
//throw new RuntimeException(e);
}finally{
/**关闭连接,释放资源**/
try {
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
总结:不要过分的细化异常,不要压制异常;早抛出,晚捕获。
--------------如果大家喜欢我的博客,可以点击左上角的关注哦。