异常处理
前言
使用异常所带来的一个相当明显的好处是,它往往能够降低错误处理代码的复杂度。如果不使用异常,那么就必须检査特定的错误,并在程序中的许多地方去处理它。而如果使用异 常,那就不必在方法调用处进行检査,因为异常机制将保证能够捕获这个错误。并且,只需在 —个地方处理错误,即所谓的异常处理程序中。这种方式不仅节省代码,而且把“描述在正常 执行过程中做什么亊”的代码和“出了问题怎么办”的代码相分离。总之,与以前的错误处理 方法相比,异常机制使代码的阅读、编写和调试工作更加井井有条。
所以这样,可以避免了我在编写程序时的写多个if判断来明细每种可能出现的类型错误。
一、什么是异常
异常的英文单词是exception,字面翻译就是“意外、例外”的意思,也就是非正常情况。事实上,异常本质上是程序上的错误,包括程序逻辑错误和系统错误。比如使用空的引用、数组下标越界、内存溢出错误等,这些都是意外的情况,背离我们程序本身的意图。错误在我们编写程序的过程中会经常发生,包括编译期间和运行期间的错误,在编译期间出现的错误有编译器帮助我们一起修正,然而运行期间的错误便不是编译器力所能及了,并且运行期间的错误往往是难以预料的。假若程序在运行期间出现了错误,如果置之不理,程序便会终止或直接导致系统崩溃,显然这不是我们希望看到的结果。因此,如何对运行期间出现的错误进行处理和补救呢?Java提供了异常机制来进行处理,通过异常机制来处理程序运行期间出现的错误。通过异常机制,我们可以更好地提升程序的健壮性。
二、为什么要用java异常处理机制
1、java的基本理念:结构不佳的代码不能运行。
2、异常处理机制初衷:方便程序员处理错误。在编译期间对可能出现的错误进行捕获---异常处理机制。如果每个方法可能出现的错误都进行情况处理,使得编码过于繁琐。
3、异常是为了方便程序员将精力放在业务的处理逻辑上。
4、进行try-catch异常处理,是为了保证程序的正常运行。
5、详细说明:
java exception是java独特的一个元素,在我理解中,它用来保证我们将精力放在正常的业务逻辑上,一般的代码本身应该主要用来实现正常业务逻辑的执 行。在正常业务逻辑执行的过程中,会产生可预知及不可预知的意外情况,例如内存泄露属于不可预知的意外情况,参数输错属于可预知的意外情况。从前我们都将 exception想得太严重,觉得是非法参数、数组越界这些很严重的情况才会用到exception,属于“不得以而处理之”的东西。现在,我们是否应 该好好利用这个java中特别的一份子,来帮助我们更好的处理业务流程,将正常流程和意外情况分割开来进行处理,以保证正常业务需求与意外事件分离。
使用异常还有一个好处是,它显示的声明了可能发生的意外情况。例如,在读取一个数据项是,也许给出的索引是错误的,在从前的代码中,比较细心些的程序员 会留个心眼检查一下调用的代码是否会返回null对象。有一些粗心的程序员会忘记这一点,默认对方会返回一个对象,然后就在自己的代码中直接调用该对象的 方法,从而在调试时,会莫名其妙的收到空指针异常。如果下层代码在撰写时对索引无效这一个意外情况封装了一个Checked类型(检查时异常)的Exception,那 么上层调用的程序员就可以在IDE的提示下获知这一可能发生的意外,从而在代码中避免调用空指针的方法而导致错误。并且,上层程序员还可以视情况的恢复异常,即使他无能为力,至少还可以再抛给上层,看看有没有人可以处理这个意外。也算是程序员之间,一种不见面的契约。
但是使用异常会有一 个问题,即接口的耦合度变高了。原来只需要参数符合,现在还强制要求处理预见的异常,很多程序员也觉得是一种负担,代码上看起来也会不简洁。而且,如果某 一个业务模块会发生的意外很多,而针对每一个意外情况都定义一种异常的话,会导致接口抛出一长串异常,使代码变得很难看。所以,如何将性质相近的异常抽象 成一个通用的异常也很考验java程序员的功力。
注:checked exception(检查异常),也称非运行时异常(运行时异常以外的异常就是非运行时异常),java编译器强制程序员必须进行捕获处理,比如常见的IOExeption和SQLException。对于非运行时异常如果不进行捕获或者抛出声明处理,编译都不会通过。
三、程序中的异常处理
3.1 异常处理的位置
【1】在最上层,方法的调用处。
【2】将相近的错误信息尽可能的整理为一个异常处理信息;
3.2 开发中对异常分析
3.2.1 业务层不进行异常处理
1、因为spring框架对事务进行了处理,如果在业务层进行try-catch,则执行失败时,事务不会回滚。
2、异常都在控制层进行处理,即所有的try-catch均写在control层
3.2.2 出现异常,报错下面的代码是否还会执行
3.2.2.1 不进行异常处理
public static void main(String[] args) { int a = 1/0; System.out.println("不进行异常处理,报错后的代码不会执行"); }
如果不进行异常处理,会阻断程序的继续执行。此时在可控制台有如下信息输出:
方法与方法调用处均不进行异常处理:
public static void main(String[] args) { int a = testException(); System.out.println(a); System.out.println("没有异常处理,直接报错"); } public static int testException() { int b = 1; b = 1/0; System.out.println("方法中不进行异常处理,也不抛出异常,后续代码均不会再执行,直接返回到方法调用处"); return b; }
控制台输出:
3.2.2.2 进行异常处理,在方法调用处进行异常处理
public static void main(String[] args) { int a = 0; try { a = testException(); } catch (Exception e){ //e.printStackTrace(); System.out.println(a); System.out.println("在方法调用处catch异常"); } System.out.println("catch后续代码继续执行"); } public static int testException() { int b = 1; b = 1/0; System.out.println("方法中不进行异常处理,也不抛出异常,后续代码均不会再执行,直接返回到方法调用处"); return b; }
控制台输出:
3.2.2.3 进行异常处理,在方法里try-catch
public static void main(String[] args) { int a = 0; a = testException(); System.out.println(a); } public static int testException() { int b = 0; try { b = 1/0; } catch (Exception e){ b = 1; System.out.println(b); System.out.println("进行异常处理,报错后对b重新赋值"); } System.out.println("进行异常处理,报错后catch下面的代码会执行"); return b; }
注意:当对程序进行了异常处理时,如果不打印输出异常信息,在控制台是没有输出的。
控制台输出信息如下:只有我自己写的标准输出。
加上e.printStackTrace()后,才会打印异常信息。
也可以通过日志管理工具进行信息输出。如logback。
详见链接:https://www.cnblogs.com/vole/p/12568493.html
3.2.2.4 进行异常处理,但是在catch中抛出(throw)异常
在方法中,既可以向上面那样,对异常进行处理,也可以抛出异常。
如下例所示,抛出异常,表示我不想在方法里处理,要抛出去统一处理。同样,在方法调用处不catch的话,同样可以继续向外抛出。
throw后面是不能写代码的,编译就不通过。
public static void main(String[] args) /*throws Exception*/{ try { testException(); } catch (Exception e){ System.out.println("在方法调用处处理异常"); } } public static int testException() throws Exception{ int a = a = 1/0; throw new Exception(); //return a;当对一个方法进行throw后,下面的代码均不会再执行 }
控制台输出:
上面只是个例子,因为实际开发明显不会这样抛出异常,不然怎么return。
想要抛出异常,直接在方法处throws就行了。如下:
public static void main(String[] args) throws Exception{ int a = 0; a = testException(); System.out.println("方法处抛出了异常,调用处没有处理异常,报错"); System.out.println(a); } public static int testException() throws Exception{ int b = 0; b= 1/0; return b; }
控制台输出:
这里输出报错,因为main主方法为最外层了,不能再抛了,必须处理。
如果不抛出的话,也不处理,编译会报错的。
也可以可以自定义异常,像下面这样抛出异常:
public static void main(String[] args) { int a = 0; a = testException(); System.out.println("方法处抛出了异常,调用处没有处理异常,报错"); System.out.println(a); } public static int testException() { int b = 0; try { b = 1/0; } catch (Exception e){ b = 1; throw new BusinessException ("b抛出自定义异常"); //System.out.println(a); //System.out.println("进行异常处理,报错后对b重新赋值"); } System.out.println("进行异常处理,报错后catch下面的代码会执行"); return b; }
控制台输出:
不仅要抛出,抛出去后要记得处理,不然就继续往外抛,最外层一定要处理,不然会报错。正确的如下,在方法调用处处理异常:
public static void main(String[] args) throws Exception{ int a = 0; try{ a = testException(); }catch(BusinessException be){ System.out.println(be.getMessage()); System.out.println("方法处抛出了异常,调用处处理异常"); }catch (Exception e){ System.out.println("走不到这里"); } System.out.println(a); System.out.println("异常处理完,继续执行后续代码"); } public static int testException() { int b = 0; try { b = 1/0; } catch (Exception e){ b = 1; throw new BusinessException ("b抛出自定义异常"); //System.out.println(a); //System.out.println("进行异常处理,报错后对b重新赋值"); } System.out.println("进行异常处理,报错后catch下面的代码会执行"); return b; }
控制台输出:
3.2.2.4 进行异常处理,在catch中return
如下示例,在catch中进行了return,当出现异常时返回1。最好就是别在catch中些return了。异常就放到调用处,control里来写。
public static void main(String[] args) { int a = 0; a = testException(); System.out.println(a); } public static int testException(){ int a = 0; try { a = 1/0; } catch (Exception e){ a = 1; return a; //System.out.println("进行异常处理,报错后对a重新赋值");//return后的代码会出现编译时异常 } a = 2; return a; }
经过我的经验,感觉try-catch用的时候一定要慎重,还有严谨,简单,如果将太多的逻辑判断放到try-catch中来判断,也就失去了异常处理机智的初衷,反而让程序看起来更臃肿。
四、自定义异常
1、简单的自定义异常
package com.asd.common.utils; public class BusinessException extends RuntimeException { private static final long serialVersionUID = 1L; public BusinessException(){ } public BusinessException(String message){ super(message); } }
2、如下所示,可以在业务层抛出自定义的异常
private Map<List<Importloss>, List<Assess>> genAssessBreakMapByloss(List<Importloss> importlossList, List<Assess> assessTreatyList) { int importlossListSize = importlossList.size();//获取上传的 Map<SDataKey, List<Importloss>> group_loss = SDataGroup.group(importlossList, "assessdate", "comcode", "classcode4", "reinstype4", "uwyear","treatyid","acccurrency","reacccurrency"); Map<SDataKey, List<Assess>> group_assess = SDataGroup.group(assessTreatyList, "assessdate", "comcode", "classcode4", "reinstype4", "uwyear","businessid","acccurrency","reacccurrency"); Set<SDataKey> sDataKeys = group_assess.keySet(); //组织map key为导入表数据,value为评估表数据 Map<List<Importloss>, List<Assess>> breakMap = new HashMap<>(); for (SDataKey dataKey : sDataKeys) { if (group_loss.containsKey(dataKey)){ breakMap.put(group_loss.get(dataKey),group_assess.get(dataKey)); importlossList.removeAll(group_loss.get(dataKey));//将 //assessTreatyList.removeAll(group_assess.get(dataKey));//将 }else{//如果 Map<SDataKey, List<Importloss>> group_loss2 = SDataGroup.group(importlossList, "assessdate", "comcode", "classcode4", "reinstype4", "uwyear","treatyid","acccurrency"); List<Assess> assessTreatyList1 = new ArrayList<>(); List<Assess> assessTreatyList2 = group_assess.get(dataKey); assessTreatyList1.addAll(assessTreatyList2); Map<SDataKey, List<Assess>> group_assess2 = SDataGroup.group(assessTreatyList1, "assessdate", "comcode", "classcode4", "reinstype4", "uwyear","businessid","acccurrency"); Set<SDataKey> sDataKeys1 = group_assess2.keySet(); for (SDataKey dataKey1 : sDataKeys1) { if (group_loss2.containsKey(dataKey1)) { breakMap.put(group_loss2.get(dataKey1), group_assess2.get(dataKey1)); } } } } if(breakMap.size() != importlossListSize){ throw new BusinessException("请校对上传的数据"); } return breakMap; }
3、在控制层catch异常,并用日志记录异常信息。可以写多个catch来具体化异常信息。
/** *拆分 * @param assessdate * @return */ @RequestMapping(value = "breakData",produces = "application/text;charset=utf-8") @ResponseBody public String breakData(String assessdate){ String result = "成功"; try { //组织拆分需查询的条件 result = outstandingBreakService.breakData(assessdate); }catch (BusinessException e){ result = e.getMessage(); log.error(result,e); } catch (Exception e){ result = "失败"; log.error(result,e); } return result; }
五、关于异常的捕获与日志管理
5.1 tomcat日志
tomcat 有五类日志 :catalina、localhost、manager、admin、host-manager
1、catalina.out
catalina.out中记录的控制台打印出的信息。(catalina.out即标准输出和标准出错,所有输出到这两个位置的都会进入catalina.out,这里包含tomcat运行自己输出的日志以及应用里向console输出的日志。默认这个日志文件是不会进行自动切割的,我们需要借助其他工具进行切割(注意:catalina.out文件如果过大会影响tomcat的运行));
2、catalina.{yyyy-MM-dd}.log
catalina.{yyyy-MM-dd}.log是tomcat自己运行的一些日志,这些日志还会输出到catalina.out,但是应用向console输出的日志不会输出到
catalina.{yyyy-MM-dd}.log,它是tomcat的启动和暂停时的运行日志,注意,它和catalina.out是里面的内容是不一样的。
3、localhost.{yyyy-MM-dd}.log
localhost.{yyyy-MM-dd}.log主要是应用初始化(listener, filter, servlet)未处理的异常最后被tomcat捕获而输出的日志,它也是包含tomcat的启动和暂停时的运行日志,但它没有catalina.2018-09-19.log 日志全。它只是记录了部分日志。
参看链接:
tomcat日志详解:https://www.cnblogs.com/operationhome/p/9680040.html
5.2 异常捕获与日志输出
当不进行异常处理时,如果程序报错,程序会中断,且在控制台是不会进行异常信息打印的;(会在 localhost.YYYY-MM-DD中记录异常信息。loglocalhost.{yyyy-MM-dd}.log主要是应用初始化(listener, filter, servlet)未处理的异常最后被tomcat捕获而输出的日志,它也是包含tomcat的启动和暂停时的运行日志,但它没有catalina.2018-09-19.log 日志全。它只是记录了部分日志。)
想要在控制台打印报错信息,必须对异常进行捕获并进行e.printStackTrace();printStackTrace()方法的意思是:在命令行打印异常信息在程序中出错的位置及原因。或者通过日志管理工具,如log4j;logback进行管理,也会打印报错信息。
参看链接:
https://www.cnblogs.com/dolphin0520/p/3769804.html
https://blog.csdn.net/hguisu/article/details/6155636
六、常见异常
1、
//报错:java.lang.ArithmeticException:divide by zero //除数不能为零,请务必检查代码是否有机会出现除数为零的情况。
2、
//Spring MVC环境报错 No mapping found for HTTP request with URI //可能原因: @Controller @RequestMapping("/outstand")=====》这里不要写value public class OutstandingBreakController { @Autowired private OutstandingBreakService outstandingBreakService; //导入/拆分 @RequestMapping("outstandBreak") public String getAssessBreakPage(){ return "break/outstandBreak"; }
3、
//异常信息: 严重: Servlet.service() for servlet [springMVC] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.transaction. UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only] with root cause org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only //==》当用Spring框架进行开发时,不能在业务层处理异常。因为Spring有自己的事务处理机制, //进行try catch后,不会进行回滚事务,会报错。而且在catch中的return也不会进行返回。
在所有的矛盾中,要优先解决主要矛盾,其他矛盾也就迎刃而解。
不要做个笨蛋,为失去的郁郁寡欢,聪明的人,已经找到了解决问题的办法,或正在寻找。