好文分享第二天——Exception和Error引申出的异常处理问题

Exception和Error

Exception表示的异常是可处理异常,一般出现在我们程序正常运行中,可以预料到的情况(如程序员编码失误、系统配置出问题)所造成的,应该被捕获或抛出进行对应的处理。
  • 第一种Exception:(checked)可检查异常,可检查异常在源代码里必须显式地进行捕获处理,这是编译期检查的一部分。
  • 第二种Exception:(unchecked)不检查异常。
Error表示的是一般程序员不可控的情况,在正常情况下不太可能会出现的,绝大部分的error错误产生都是发生在JVM等使程序处于非正常、不可恢复的状态。既然是非正常状态,所以不便于进行捕获。
  • Exception和error都继承于Throwable类,只有Throwable类才能被捕获(catch)或者抛出(throw)。

try-with-resources和multiple catch

在我们学习时可能接触较多的使用是这样的

FileReader fr = null;  
BufferedReader br = null;

try {
    fr = new FileReader(fileName);
    br = new BufferedReader(fr);
    return br.readLine();
} catch (Exception e) {
    log.error("error:{}", e);
} finally {
  if (br != null) {
    try {
      br.close();
    } catch(IOException e){
      log.error("error:{}", e);
    }
  }

  if (fr != null ) {
    try {
      br.close();
    } catch(IOException e){
      log.error("error:{}", e);
    }
  }
}

而JDK7以后加入了try-with-resources的语法,如下

try (
    FileReader fr = new FileReader(fileName);
    BufferedReader br = new BufferedReader(fr)
  ) {
    return br.readLine();
}catch (Exception e) {
    log.error("error:{}", e);
}

或者

try (
    BufferedReader br = new BufferedReader(new FileReader(fileName))
  ) {
    return br.readLine();
}
catch (Exception e) {
    log.error("error:{}", e);
}

而输入输出流的关闭呢?查看源代码发现

public class FileInputStream extends InputStream{}
public abstract class InputStream implements Closeable {}
public interface Closeable extends AutoCloseable {}
public interface AutoCloseable {}
  • AutoCloseable:自动关闭流,外部资源的句柄对象只要实现了这个接口就可以被自动关闭。
  • 而这种语法实现的原理是:将外部资源的句柄对象的创建放在try关键字后面的括号中,当这个try-catch代码块执行完毕后,Java会确保外部资源的close方法被调用。
如果还想了解更深入,通过反编译,发现虚拟机所执行的依旧是老版本的语法,这时在代码中又发现了一些不同的东西
try {
  inputStream.close();
} catch (Throwable var11) {
  var2.addSuppressed(var11);  //这里是关键
}

Throwable类新增了addSuppressed方法,支持将一个异常附加到另一个异常身上,从而避免异常屏蔽。

这段对异常的特殊处理就是一个新的知识点:异常抑制。所谓异常抑制,就是当对外部资源进行处理(例如读或写)时,如果遭遇了异常,且在随后的关闭外部资源过程中,又遭遇了异常,那么你catch到的将会是对外部资源进行处理时遭遇的异常,关闭资源时遭遇的异常将被“抑制”但不是丢弃,通过异常的getSuppressed方法,可以提取出被抑制的异常。

简单的说,“关闭异常”将被抑制,“处理异常”将被抛出,但“关闭异常”并没有丢失,而是存放在“处理异常”的被抑制的异常列表中。

注意:在try-with-resources 语句中仍然有catch块和finally块, 任意的 catch 或者 finally 块都是在声明的资源被关闭以后才运行。

介绍完了try-with-resources,接下来介绍multiple catch

multiple catch:顾名思义,就是在一个catch块中同时捕获多个异常,做个对比,JDK7以前

catch (IOException ex) {
     logger.error(ex);
     throw new MyException(ex.getMessage());
catch (SQLException ex) {
     logger.error(ex);
     throw new MyException(ex.getMessage());
}catch (Exception ex) {
     logger.error(ex);
     throw new MyException(ex.getMessage());
}

JDK7以后使用multiple catch

catch(IOException | SQLException | Exception ex){
     logger.error(ex);
     throw new MyException(ex.getMessage());
}

代码优雅简洁了很多是不是哈哈!!!


异常处理的两个基本原则

第一,尽量不要捕获类似 Exception 这样的通用异常,而是应该捕获特定异常。
  • 这是方便我们读代码,如果都是Exception,则不能直观的让你知道是哪里出的错误,同时,我们要保证程序不会捕获到我们不希望捕获的异常,比如,你可能更希望 RuntimeException 被扩散出来,而不是被捕获。
  • 不要捕获Throwable或者Error,这样很难保证我们能够正确程序处理OutOfMemoryError。
第二,不要生吞(swallow)异常。这是异常处理中要特别注意的事情,因为很可能会导致非常难以诊断的诡异情况。
  • 要不就抛出异常,要不就输出到日志Logger中,否则会造成维护时或者看代码时无法判断究竟是哪里抛出了异常,以及是什么原因产生了异常。
  • 同时对于e.printStackTrace();这个输出在产品代码中也是不允许使用的,因为这是个标准出错的输出,标准出错(STERR)不是个合适的输出选项,因为你很难判断出到底输出到哪里去了。尤其是对于分布式系统,如果发生异常,但是无法找到堆栈轨迹(stacktrace),这纯属是为诊断设置障碍。所以,最好使用产品日志,详细地输出到日志系统里。
  • throw early,catch late

自定义异常

1.如果希望写一个检查性的异常,则继承Exception;如果希望写一个运行时的异常,那么继承RuntimeException;所有的异常必须是Throwable的子类
2.是否需要定义成 Checked Exception,因为这种类型设计的初衷更是为了从异常情况恢复,作为异常设计者,我们往往有充足信息进行分类。
3.在保证诊断信息足够的同时,也要考虑避免包含敏感信息,因为那样可能导致潜在的安全问题。如果我们看 Java 的标准类库,你可能注意到类似 java.net.ConnectException,出错信息是类似“ Connection refused (Connection refused)”,而不包含具体的机器名、IP、端口等,一个重要考量就是信息安全。类似的情况在日志中也有,比如,用户数据一般是不可以输出到日志里面的。

性能分析

1.try-catch产生的性能开销是较大的,所以我们应该尽量不要把所有逻辑代码都包含在try块中,而应该是把需要try-catch的内容包含在其中即可
2.Java 每实例化一个 Exception,都会对当时的栈进行快照,这是一个相对比较重的操作。如果发生的非常频繁,这个开销可就不能被忽略了。
3.当我们的服务出现反应变慢、吞吐量下降的时候,检查发生最频繁的Exception是一种思路。
思考:对于部分追求极致性能的底层类库,尝试创建不进行栈快照的 Exception是否可行?

个人总结

1.不要在finally代码块中处理返回值。
2.在try-catch语句中,如果有finally块存在,除了return语句,try代码块中的break或continue语句也可能使控制权进入finally代码块
3.请勿在try代码块中调用return、break或continue语句。万一无法避免,一定要确保finally的存在不会改变函数的返回值。
4.函数返回值有两种类型:值类型与对象引用。对于对象引用,要特别小心,如果在finally代码块中对函数返回的对象成员属性进行了修改,即使不在finally块中显式调用return语句,这个修改也会作用于返回值上。

思考

1.NoClassDefFoundError和ClassNotFoundException有什么区别
2.如何自定义异常

NoClassDefFoundError和ClassNotFoundException有什么区别

1.NoClassDefFoundError产生的原因在于:如果JVM或者ClassLoader实例尝试加载(可以通过正常的方法调用,也可能是使用new来创建新的对象)类的时候却找不到类的定义。要查找的类在编译的时候是存在的,运行的时候却找不到了。这个时候就会导致NoClassDefFoundError。
造成该问题的原因可能是打包过程漏掉了部分类,或者jar包出现损坏或者篡改。解决这个问题的办法是查找那些在开发期间存在于类路径下但在运行期间却不在类路径下的类。
2.ClassNotFoundException的产生原因主要是:Java支持使用反射方式在运行时动态加载类,例如使用Class.forName方法来动态地加载类时,可以将类名作为参数传递给上述方法从而将指定类加载到JVM内存中,如果这个类在类路径中没有被找到,那么此时就会在运行时抛出ClassNotFoundException异常。
解决该问题需要确保所需的类连同它依赖的包存在于类路径中,常见问题在于类名书写错误。
另外还有一个导致ClassNotFoundException的原因就是:当一个类已经被某个类加载器加载到内存中了,此时另一个类加载器又尝试着动态地从同一个包中加载这个类。通过控制动态类加载过程,可以避免上述情况发生。

参考于:

杨大的Jvaa核心技术
https://blog.csdn.net/wangjie_19920912/article/details/69501883
http://www.importnew.com/7015.html

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 精致技术 设计师:CSDN官方博客 返回首页