Java核心技术卷I:基础知识(原书第8版):11.4 断言

铁文整理

11.4 断言

    在一个具有自我保护能力的程序中,断言是一个常用的习语。假设确信某个属性符合要求,并且代码的执行依赖于这个属性。例如,需要计算

        double y = Math.sqrt(x);

    我们确信,这里的x是一个非负数值。原因是:x是另外一个计算的结果,而这个结果不可能是负值;或者x是一个方法的参数,而这个方法要求它的调用者只能提供一个正整数。然而,还是希望进行栓査,以避免让错误的数值参与计算操作。当然,也可以抛出一个异常:

        if (x < 0)

            throw new IllegalArgumentException("x < 0");

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

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

    Java SE 1.4中,Java语言引入了关键字assert。这个关键字有两种形式:

    assert 条件;

   

    assert 条件 : 表达式;

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

    注释:“表达式”部分的惟一目的是产生一个消息字符串。AssertionError对象并不存储表达式的值,因此,不可能在以后得到它。正如JDK文档所描述的那样:如果使用表达式的值,就会鼓励程序员试图从断言中恢复程序的运行,这不符合断言机制的初衷。

    要想断言x是一个非负数值,只需要简单地使用下面这条语句:assert x >= 0;或者将x的实际值传递给AssertionError对象,从而可以在后面显示出来:assert x >= 0 : x;

    C++注释:C语言中的assert宏将断言中的条件转换成一个字符串。当断言失败时,这个字符串将会被打印出来。例如,若assert(x>=0)失败,那么将打印出失败条件x>=0。在Java中,条件并不会自动地成为错误报告中的一部分。如果希望看到这个条件,就必须将它以字符串的形式传递给入AssertionError对象:assert x >= 0 : "x >= 0";

11.4.1 启用和禁用断言

    在默认情况下,断言被禁用。可以在运行程序时用-enableassertion-ea选项启用它:

    java -enableassertions MyApp

    需要注意:在启用或禁用断言时不必重新编译程序。启用或禁用断言是类加载器的功能。当断言被禁用时,类加载器将跳过断言代码,因此,不会降低程序运行的速度。

    也可以在某个类或某个包中使用断言。例如,

    java -ea:MyCla5S -ea:com.mycompany.mylib... MyApp

    这条命令将开启MyClass类以及在com.mycompany.mylib包和它的子包中的所有类的断言。选项-ea将开启默认包中的所有类的断言。

    也可以用选项-disableassertions-da禁用某个特定类和包的断言:

    java -ea:... -da:MyClass MyApp

    有些类不是由类加载器加载,而是直接由虚拟机加载。可以使用这些开关有选择地启用或禁用那些类中的断言。然而,启用和禁用所有断言的-ea-da开关不能应用到那些没有类加载器的“系统类”上。对于这些系统类来说,需要使用-enablesystemassertions/-esa开关启用断言。

    在程序中也可以控制类加载器的断言状态。有关这方面的内容请参看本节末尾的API注解。

11.4.2 使用断言的建议

    Java语言中,给出了三种处理系统错误的机制:

  • 抛出一个异常

  • 日志

  • 使用断言

    什么时候应该选择使用断言呢?请记住下面几点:

  • 断言失败是致命的、不可恢复的错误。

  • 断言检查只用于开发和测阶段(这种做法有时候被戏称为“在靠近海岸时穿上救生衣,但在海中央时就把救生衣抛入水中吧”)。

    因此,不应该使用断言向程序的其他部分通告发生了可恢复性的错误,或者,不应该作为程序向用户通告问题的手段。断言只应该用于在测试阶段确定程序内部的错误位置。

    下面看一个十分常见的例子:检査方法的参数。是否应该使用断言来检査非法的下标值或null引用呢?要想回答这个问题,首先阅读一下这个方法的文档。假设实现一个排序方法。

    /**

     * Sorts the specified range of the specified array into ascending numerical

     * order. The range to be sorted extends from fromIndex, inclusive, to

     * toIndex, exclusive.

     *

     * @param a

     *            the array to be sorted.

     * @param fromIndex

     *            the index of the first element (inclusive) to be sorted.

     * @param toIndex

     *            the index of the last element (exclusive) to be sorted.

     * @Throws IllegalArgumentException if fromIndex > toIndex

     * @Throws ArrayIndexOutOfBoundsException if fromIndex < 0 or tolndex>

     *         a.length

     */

    static void sort(int[] a, int fromIndex, int toIndex)

    文档说明,如果方法中使用了错误的下标值,那么就会抛出一个异常。这是方法与调用者之间约定的处理行为。如果实现这个方法,那就必须要遵守这个约定,并抛出表示下标值有误的异常,因此,这里使用断言不太适宜。

    是否应该断言a不是null吗?这也不太适宜。当anull时,这个方法的文档没有指出应该采取什么行动。在这种情况下,调用者可以认为这个方法将会成功地返回,而不会抛出一个断言错误。

    然而,假设对这个方法的约定做一点微小的改动:

     * @param a

     *            the array to be sorted. (Must not be null)

    现在,这个方法的调用者就必须注意:不允许用null数组调用这个方法,并在这个方法的开头使用断言

        assert a != null;

    计算机科学家将这种约定称为前提条件。最初的方法对参数没有前提条件,即承诺在任何条件下都能够给予正确的执行。修订后的方法有一个前提条件,即a非空。如果调用者在调用这个方法时没有提供满足这个前提条件的参数,所有的断言都会失败,并且这个方法可以执行它想做的任何操作。事实上,由于可以使用断言,当方法被非法调用时,将会出现难以预料的结果。有时候会抛出一个断言错误,有时候会产生一个null指针异常,这完全取决于类加载器的配置。

11.4.3 为文档使用断言

    很多程序员使用注释说明假设条件。看一下http://java.sun.com/javase/6/docs/technotes/ guides/language/assert.html上的一个示例:

        if (i % 3 == 0) ...

        else if (i % 3 == 1) ...

        else ... // (i % 3 == 2)

    在这个示例中,使用断言会更好一些。

        if (i % 3 == 0) ...

        else if (i % 3 == 1) ...

        else{

            assert i % 3 == 2;

            ...

        }

    当然,如果再仔细地考虑一下这个问题会发现一个更有意思的内容。i%3会产生什么结果?如果i是正值,那余数肯定是012。如果i是负值,则余数可以是-1-2。然而,实际上都认为i是非负值,因此,最好在if语句之前使用下列断言:

        assert i >= 0;

    无论如何,这个示例说明了程序员如何使用断言来进行自我检査。前面已经知道,断言是一种测试和调试阶段所使用的战术性工具,而日志记录是一种在程序的整个生命周期都可以使用的策略性工具。稍后将介绍日志的相关知识。

APIjava.lang,ClassLoader 1.0

  • void setDefaultAssertionStatus(boolean b) 1.4:对于通过类加载器加载的所有类来说,如果没有显式地说明类或包的断言状态,就启用或禁用断言。

  • void setCIassAssertionStatus(String className, boolean b) 1.4:对于给定的类和它的内部类,启用或禁用断言。

  • void setPackageAssertionStatus(String packageName, boolean b) 1.4:对于给定包和其子包中的所有类,启用或禁用断言。

  • void clearAssertionStatus() 1.4:移去所有类和包的显式断言状态设置,并禁用所有通过这个类加载器加载的类的断言。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值