JAVA和Nginx 教程大全

网站首页 > 精选教程 正文

月薪3万以上的程序员必备技能:Java 中的异常和处理详解

wys521 2024-11-20 22:51:23 精选教程 14 ℃ 0 评论

本文参考自阿里孤尽老师的《码出高效代码》一书的第五章,为了节省大家学习时间,在这里对重要知识进行了总结,如果有知识点不明白的可以关注作者,找作者要相关的具体学习资料(免费送上)。

处理异常需要解决的问题

  • 哪里发生异常?
  • 可以通过try-catch捕获异常。不建议用try-catch捕获大代码块,同时捕获异常时要分清稳定代码(无论如何都不会出错的代码,如int i = 0)和非稳定代码(有可能出错的代码。如:Integer.valueOf(String s)转换失败时抛出NumberFormatException异常)。

  • 谁来处理异常?
    • throw:用于方法内抛出具异常类对象的关键字。
    public static Long getDateTime(String time) throws ParseException {
     if (org.apache.commons.lang.StringUtils.isEmpty(time) || time.length() != 8) {
     throw new BizException("单元格日期为空");
     }
     SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
     dateFormat.setLenient(false);
     return dateFormat.parse(time).getTime();
    }
    • throws:用在方法签名上,表示方法调用者可以通过此方法声明向上抛出异常对象。
    public static Integer valueOf(String s) throws NumberFormatException {
     return Integer.valueOf(parseInt(s, 10));
    }

    如果异常在当前方法处理能力范围之内则没必要对外透出,直接捕获并做相应处理即可。否则向上抛出,由上层方法或框架来处理。

  • 如何处理异常?
  • 禁止异常捕获后什么都不做或打印一行日志了事

    • 方法内处理:需要根据业务场景定制处理,如重试、回滚等操作。
    • 向上抛异常:需要在异常对象中添加上下文参数、局部变量、运行环境等信息,有利于排查问题。

    异常分类

    JDK中定义了一套完整的异常机制,所有异常都是Throwable的子类分为Error(致命异常)和Exception(非致命异常)。Error是一种非常特殊的异常类型(程序无法处理,只能人工介入),它的出现标识着系统发生了不可控的错误,如StackOverflowError、OutOfMemoryError。Exception又分为checked异常(受检异常)和unchecked(非受检异常)

    • checked异常需要在代码中显式处理的异常,否则会编译出错。如:SQLException、ClassNotFoundException等。checked异常又可细分为:
      • 无能为力、引起注意型。即程序无法处理,如字段超长等导致的SQLException。处理方式:保存异常现场,工程师介入。
      • 力所能及、坦然处置型。如发送未授权异常,程序可以直接跳转至权限申请页面。
    • unchecked异常运行时异常,都继承自RuntimeException,不需要显式的捕获和处理。unchecked异常又可细分为:
      • 可预测异常:包括IndexOutOtBoundsException、NullPointerException等异常。基于代码性能和稳定性考虑,此类异常不应该被产生或抛出,应该提前做好边界检查、空指针判断等处理
      • 需捕捉异常:如使用Dubbo框架RPC调用产生的远程调用超时异常DubboTimeoutException,客户端必须显式处理(重试、降级处理等),不能因为服务器异常导致客户端不可用。
      • 可透出异常:指框架或系统产生的且会自行处理的异常,而程序无须关心。

    异常分类结果图如下:

    最后,处理异常时,要明确该异常属于哪种异常类型,是需要调用方关注并处理的checked异常,还是由更高层次框架处理的unchecked异常。无论哪类异常,如果需要向上抛出,推荐根据当前场景自定义具有业务含义的异常。(不推荐直接使用RuntimeException、Exception等没有业务含义的异常类,同时为了避免异常泛滥,推荐优先使用业界或团队定义过的异常

    try代码块

    try-catch-finally是处理异常的三部剧。当存在 try 时,可以只有 catch 代码块, 也可以只有 finally 代码块,就是不能单独只有 try 这个光杆司令。

  • try代码块: 监视代码执行过程,一旦发现异常则直接跳转至catch,如果没有catch,则直接跳转至finally。
  • catch代码块: 可选执行代码,如果没有任何异常发生则不会执行;如果发现异常则可以在catch代码块中进行处理或向上抛出。
  • finally代码块: 必须执行代码块不管是否发生异常,即使发送OutOfMemoryError也会执行。通常用于处理善后清理工作。finally代码块没有执行的三种可能(很多恶心的面试官会问)
    • 没有进入try代码块。
    • 进入try代码块,但是代码运行中出现死循环或死锁状态。
    • 进入try代码块。但是执行了System.exit()操作。

    注意: finally是在return表达式运行后执行的,此时return的结果已经被缓存起来,待finally代码块执行结束后再将之前暂存的结果返回。示例代码如下:

    //结果返回1
    public int getFinallyResult(){
     int a = 0; //1 初始化变量
     try{
     a = 1; //2 变量重新赋值
     return a; // 3 将a的值缓存 5将第3步缓存的值返回
     }finally {
     a++; //4 将a值进行a++操作后a的值为2
     }
    }

    禁止finally代码块中使用return语句。因为在finally中使用return会使得返回值的判断变得复杂,不可控。

    try代码块与锁关系:

    //错误示列
    Lock lock = new ReentrantLock();
    try{
     //lock.lock()放在try{}中,如果lock.lock()加锁失败,则会导致lock.unlock()解锁失败。
     //将lock.lock()放在try{}外第一行即可,这样lock.lock()加锁失败就失败,不会触发		 
     //lock.unlock()。
     lock.lock();
     //处理一些工作
    }finally {
     lock.unlock();
    }
    
    //正确示列
    Lock lock = new ReentrantLock();
    lock.lock();
    try{
     //处理一些工作
    }finally {
     lock.unlock();
    }
    //正确示列
    Lock lock = new ReentrantLock();
    lock.lock();
    try{
     //处理一些工作
    }finally {
     lock.unlock();
    }

    最后:Lock、ThreadLocal、InputStream 等这些需要进行强制释放和清除的对象都得在 finally 代码块中进行显式的清理,避免产生内存泄漏或资源消耗。

    异常的抛与接

    传递异常信息的方式是通过抛出异常对象,还是把异常信息转成信号量封装在特定对象中,这需要方法提供者和方法调用者之间达成契约,只有大家都照章办事,才不会产出误解。推荐对外提供的开放接口使用错误码,公司内部跨应用远程服务调用优先考虑使用 Result 对象来封装错误码、错误描述信息;而应用内部则推荐直接抛出异常对象。

    方法是否直接返回null: 不强制直接返回空集合或者空对象等,但是必须添加注释充分说明什么情况下会返回null值。防止NPE一定是调用方的责任,需要调用方进行事先判断。

    END

    点赞+转发+关注,私信作者“读书笔记”即可获得BAT大厂面试资料、高级架构师VIP视频课程等高质量技术资料。

    本文暂时没有评论,来添加一个吧(●'◡'●)

    欢迎 发表评论:

    最近发表
    标签列表