你现在就必须知道的Java异常体系

版权声明:本文为博主原创文章,如需转载请标明出去。 https://blog.csdn.net/sujin_/article/details/80712431

前言:对于异常情况,例如,可能造成程序崩溃的错误输入,Java是通过捕获机制来处理异常错误。当程序出错时,我们不可能总是及时和用户沟通,所以希望记录出现的问题,以备日后进行分析。

如何处理错误

当一个用户在运行程序期间,由于程序的错误或一些外部环境的影响造成用户数据的丢失,用户就有可能不再使用这个程序了。为了避免这类事情发生,我们应该注意以下几点。

  1. 向用户通知错误的原因
  2. 保存所有的工作结果(日志)
  3. 允许用户以妥善的形式退出程序

异常分类

所有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;
    }

总结:不要过分的细化异常,不要压制异常;早抛出,晚捕获。

--------------如果大家喜欢我的博客,可以点击左上角的关注哦。

猜你喜欢

转载自blog.csdn.net/sujin_/article/details/80712431