7.异常、断言和日志

1.概述

  • Java提供异常处理机制来处理异常
  • 断言:在测试中,不需要写测试代码,测试完将代码删除。我们使用断言来有选择性的进行测试
  • 日志:记录下出现的问题,已备日后分析

2.处理错误

2.1 异常分类

exception.png
  • Error:描述了运行时系统的我内部错误等。一般除了通知用户,并尽力使程序安全终止外,无能为力
  • Exception:根据unchecked分为unchecked Exception和checked Exception
    • RuntimeExcetion:unchecked Exception。
      • NullPointerException
      • ClassCastException
      • ArrayIndexOutOfBoundsException
    • IOException:checked Exception
      • 如试图打开一个不存在的文件
      • 试图在文件尾部后面读取数据
  • 编译器会检查是否为所有的checked Exception提供了异常处理器。也就是编译期间,编译器会检查你有没有对IOException进行异常处理,但是不会关心你否处理了数组越界异常

2.2 声明checked异常

  • 每个包含的checked异常的方法都需要通过throws声明具体可能会抛出什么异常,包含多个checked Exception则需要声明多个。
  • 什么时候需要抛出异常?
    • 调用会抛出checked Exception的方法时
    • 方法本身利用throw,抛出异常时
    • 程序出现运行时异常
    • 系统和内部错误
  • 什么时候需要声明异常呢?
    • 上述前两种情况
    • 不需要声明系统错误以及运行时的unchecked Exception,也就是RuntimeException。我们应该尽力去修正这些RuntimeException。
    • 总之,一个方法必须声明所有可能抛出的受查异常,而非受查异常要么不可控制(Error),要么就应该避免发生(RuntimeException)
  • 异常可以被抛出,也可以被捕获,具体采用什么策略要看具体情况

3.捕获异常

3.1 捕获异常

  • 捕获异常,必须设置try/catch语句块
  • 如果调用了一个抛出受查异常的方法,就必须对它进行处理,或者继续传递。
  • 通常,应该捕获那些知道如何处理的异常,而将那些不知道怎样处理的异常继续进行传递
  • 如果想传递一个异常,就必须在方法的首部添加一个throws说明符,以便告知调用者这个方法可能会抛出异常
  • 注意:如果编写一个覆盖超类的方法,而这个方法又没有抛出异常(如JComponent中的paintComponent),那么这个方法就必须捕获方法代码中出现的每一个受查异常。不允许在子类的throws说明符中出现超过超类方法所列出的异常类范围

3. 2 捕获多个异常

  • 在一个try语句块中,可以捕获多个异常,并对不同类型的异常做出不同的处理

3.3 再次抛出异常和异常链

  • catch子句中可以抛出异常,这样做的目的是改变异常的类型,如:
      try
      {
      }
      catch(SQLException e)
      {
        throw new ServletException();
      }
    
  • 另一种更好的办法:将原始异常作为新异常的原因
      try
      {
      }
      catch(SQLException e)
      {
        Throwable se = new ServletException();
        se.initCause(e);
        throw se;
      }
    
    获取到异常后,使用下面这条语句重新得到异常:se.getCause()
  • 如果在一个方法中发生了一个受查异常,而不允许抛出它,那么包装技术就十分有用。我们可以捕获这个受查异常,并将它包装成一个运行时异常

3.4 finally子句

  • 当finally子句包含return语句时,将会出现一种意想不到的结果。假设利用return语句从try语句块中退出。在方法返回前,finally子句的内容将被执行。如果finally子句中也有一个return语句,这个返回值将会覆盖原始的返回值。请看一个复杂的例子:
      public static int f(int n)
      {
        try
        {
          int r=2;
          return r;
        }
        finally
        {
          if (r=2) return 0;
        }
      }
    

3.5 带资源的try语句

  • 带资源的try语句:try块退出时或存在一个异常时,会自动调用res.close()
  • 如果try块抛出一个异常,而且close方法也抛出一个异常,这就会带来一个难题。带资源的try语句可以很好地处理这种情况。原来的异常会重新抛出,而close方法抛出的异常会“被抑制”。这些异常将自动捕获,并由addSuppressed方法增加到原来的异常。如果对这些异常感兴趣,可以调用getSuppressed方法,它会得到从close方法抛出并被抑制的异常列表。

3.6 分析堆栈轨迹元素

  • 堆栈轨迹(stack trace)是一个方法调用过程的列表,它包含了程序执行过程中方法调用的特定位置
  • 可以调用Throwable类的printStackTrace方法访问堆栈轨迹的文本描述信息
  • 一种更灵活的方法是使用getStackTrace方法,它会得到StackTraceElement对象的一个数组,可以在你的程序中分析这个对象数组
  • StackTraceElement类含有能够获得文件名和当前执行的代码行号的方法,同时,还含有能够获得类名和方法名的方法

4.使用异常机制的技巧

  1. 异常处理不能代替简单的测试
    对比下面两段代码:
if(!s.empty)s.pop;
与
try
{
}
catch(EmptyStackException e)

捕获异常花费的执行时间相比于前者是巨大的,因此:只在异常情况下使用异常机制

  1. 利用异常层次结构
    不要只抛出RuntimeException异常。应该寻找更加适当的子类或创建自己的异常类
    不要只捕获Thowable异常,否则,会使程序代码更难读、更难维护
  2. 不要压制异常
  3. 在检测错误时,“苛刻”要比放任更好
    例如,当栈空时,Stack.pop是返回一个null,还是抛出一个异常?我们认为:在出错的地方抛出一个EmptyStackException异常要比在后面抛出一个NullPointerException异常更好
  4. 不要羞于传递异常

4-5总结起来就是”早抛出,晚捕获“

5.断言

5.1 断言是什么

double y = Math.sqrt(x)

假设我们编写了以上代码,此时我们需要检测x是否大于0,我们可以这样做:

if (x<0) throw new IllegalArgumentException();

但是这样这段代码会一直保留在程序中,即使测试完毕也不会自动删除。如果程序中含有大量这种检查,程序运行起来会相当慢

  • 断言机制允许在测试期间向代码中插入一些检查语句。当代码发布时,这些插入的检测语句将会被自动移走。

5.2 断言的使用

assert 条件;
或
assert 条件:表达式;

这两种形式都会对条件进行检测,如果结果为false,则抛出一个AssertionError异常。在第二种形式中,表达式将被传入AssertionError的构造器,并转换成一个消息字符串

5.3 什么时候使用断言

java中给出了3种处理系统异常的机制:

  1. 抛出一个异常
  2. 日志
  3. 断言
  • 什么时候使用断言
    错误1. 断言失败是致命的、不可恢复的错误
  1. 断言检查只用于开发和测阶段

因此不应该使用断言向程序的其他部分通告发生了可恢复性的

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容