异常处理

前言

使用异常所带来的一个相当明显的好处是,它往往能够降低错误处理代码的复杂度。如果不使用异常,那么就必须检査特定的错误,并在程序中的许多地方去处理它。而如果使用异 常,那就不必在方法调用处进行检査,因为异常机制将保证能够捕获这个错误。并且,只需在 —个地方处理错误,即所谓的异常处理程序中。这种方式不仅节省代码,而且把“描述在正常 执行过程中做什么亊”的代码和“出了问题怎么办”的代码相分离。总之,与以前的错误处理 方法相比,异常机制使代码的阅读、编写和调试工作更加井井有条。
所以这样,可以避免了我在编写程序时的写多个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也不会进行返回。
如果错过太阳时你流了泪,那你也要错过群星了。
在所有的矛盾中,要优先解决主要矛盾,其他矛盾也就迎刃而解。
不要做个笨蛋,为失去的郁郁寡欢,聪明的人,已经找到了解决问题的办法,或正在寻找。
原文地址:https://www.cnblogs.com/szrs/p/12189597.html