断言的使用
•那么,为什么要使用断言,而不使用传统方法(比如if-then-else,switch-case-default或者try-catch)呢?断言是专为调试而设计的,其他方法则主要用于生产。通过断言,可检测自己的逻辑是否正确,而不是通过它来捕捉不可控制的异常。
•断言是最快和最有效的调试方式
断言与传统代码的比较
•If-then-else风格:
if (i % 3 != 0) {
if (i < 0)
{
System.err.println("Error in Variable i");
return -1;
}
System.out.println("Change $"+i%3);
}
if (i % 3 != 0) {
if (i < 0)
{
System.err.println("Error in Variable i");
return -1;
}
System.out.println("Change $"+i%3);
}
•断言风格:
if (i % 3 != 0) {
assert i > 0;
System.out.println("Change $"+i%3);
}
以上两段代码的工作方式几乎完全相同。如果变量i小于0(这是不正常的),第一段代码会报告Error in Variable i。第二段代码则引发一个断言错误,指出发生错误的行号。显然,断言方式所需的行数要少得多。
if (i % 3 != 0) {
assert i > 0;
System.out.println("Change $"+i%3);
}
以上两段代码的工作方式几乎完全相同。如果变量i小于0(这是不正常的),第一段代码会报告Error in Variable i。第二段代码则引发一个断言错误,指出发生错误的行号。显然,断言方式所需的行数要少得多。
语法
|
例子
|
assert Expression1;
|
assert i%3==2;
|
assert Expression1 : Expression2;
|
assert i%3==2 : "Wow, Error";
|
throw new AssertionError(Expression1);
|
throw new AssertionError("Oh no");
|
使用断言时的编译和运行命令
•在代码中插入断言语句后,需要编译和运行代码,但具体采用的方式有别于从前。使用断言时,要用以下命令行语句进行编译:
–javac -source 1.4 MyCode.java
-source选项强迫编译的代码提供与指定版本(1.4)的源码兼容性。这是必需的,因为断言是从JDK 1.4开始新增的功能。程序编译成与版本1.4兼容后,要用以下命令来运行程序:
-source选项强迫编译的代码提供与指定版本(1.4)的源码兼容性。这是必需的,因为断言是从JDK 1.4开始新增的功能。程序编译成与版本1.4兼容后,要用以下命令来运行程序:
–java -ea MyCode
-ea选项要求系统在程序执行期间启用断言特性。如果省略该选项,程序就不会显示断言异常消息。
如果只想在一个指定的包中启用断言,比如在com.lawrence.teki.chan包中,你可在-ea选项之后包括这个包:
java -ea:com.lawrence.teki.chan MyCode
-ea选项要求系统在程序执行期间启用断言特性。如果省略该选项,程序就不会显示断言异常消息。
如果只想在一个指定的包中启用断言,比如在com.lawrence.teki.chan包中,你可在-ea选项之后包括这个包:
java -ea:com.lawrence.teki.chan MyCode
assertion与继承
•如果开启一个子类的assertion,那么它的父类的assertion是否执行?
–下面的例子将显示如果一个assert语句在父类,而当它的子类调用它时,该assert为false。我们看看在不同的情况下,该assertion是否被处理。
class Base{
public void baseMethod(){
assert false : “Assertion failed:This is base ”;// 总是assertion失败
System.out.println(“Base Method”);
}
}
class Derived extends Base{
public void derivedMethod(){
assert false: “Assertion failed:This is derive”;// 总是assertion失败
System.out.println( "Derived Method" );
}
public static void main( String[] args ){
try{
Derived derived = new Derived();
derived.baseMethod( );
derived.derivedMethod();
}
catch( AssertionError ae ){
System.out.println(ae);
}
}
}
class Base{
public void baseMethod(){
assert false : “Assertion failed:This is base ”;// 总是assertion失败
System.out.println(“Base Method”);
}
}
class Derived extends Base{
public void derivedMethod(){
assert false: “Assertion failed:This is derive”;// 总是assertion失败
System.out.println( "Derived Method" );
}
public static void main( String[] args ){
try{
Derived derived = new Derived();
derived.baseMethod( );
derived.derivedMethod();
}
catch( AssertionError ae ){
System.out.println(ae);
}
}
}
运行命令
|
含义
|
结果
|
Java Derived
|
不启用assertion
|
Base Method
Derived Method
|
Java -ea Derived
|
开启所有assertion
|
Java.lang.AssertionError:Assertion Failed:This is base
|
Java -da Derived
|
关闭所有assertion
|
Base Method
Derived Method |
Java -ea:Base Derived
|
仅打开Base的assertion
|
Java.lang.AssertionError:Assertion Failed:This is base
|
Java -ea:Derived Derived
|
仅打开Derived的assertion
|
Base Method
Java.lang.AssertionError:Assertion Failed:This is derived |
断言使用准则
–这将涉及到程序的风格,assertion运用的目标,程序的性质等问题
–断言应被视为对程序进行验证的一种工具,而不是把它当作程序的一部分
•assertion 不适用的地方
–不适合在公共方法中使用断言参数检查,参数检查通常是一个合约的一部分,无论断言是否启用都应该执行。错误发生时,应该引发恰当的运行时异常,比如IndexOutOfBoundsException或者NullPointerException,而不是引发常规的断言错误。
•检查控制流:
–在if-then-else和swith-case语句中,我们可以在不应该发生的控制支流上加上assert false语句。如果这种情况发生了,assert能够检查出来
例如:x取值只能是1,2,3,我们的程序可以如下表示:
switch (x){
例如:x取值只能是1,2,3,我们的程序可以如下表示:
switch (x){
case 1: …;
case 2: …;
case 3: …
default: assert false:"x value is invalid: "+x;
}
case 2: …;
case 3: …
default: assert false:"x value is invalid: "+x;
}
•在私有函数计算前,检查输入参数是否有效:
–对于公共函数,我们通常不使用assertion检查,因为一般来说,公共函数必须对无效的参数进行检查和处理。而私有函数往往是直接使用的。
例如:某函数可能要求输入的参数必须不为null。那么我们可以在函数的一开始加上:
例如:某函数可能要求输入的参数必须不为null。那么我们可以在函数的一开始加上:
assert parameter1!=null : “paramerter is null in test method”;
•在函数计算后,检查函数结果是否有效:
–对于一些计算函数,函数运行完成后,某些值需要保证一定的值域。
例如:我们有一个计算绝对值的函数,那么我们就可以在函数的结果处,加上一个语句:
assert value>=0:“Value should be bigger than 0:”+value;
通过这种方式,我们可以对函数计算完的结果进行检查。
通过这种方式,我们可以对函数计算完的结果进行检查。
•检查程序不变量
–有些程序中,存在一些不变量,在程序的运行生命周期,这些不变量的值都是不变的。这些不变量可能是一个简单表达式,也可能是一个复杂的表达式。对于一些关键的不变量,我们可以通过assert进行检查。
例如,在一个财会系统中,公司的支出和收入必须保持一定的平衡关系,因此我们可以编写一个表达式检查这种平衡关系,如下表示:
private boolean isBalance() {
……
}
在这个系统中,在一些可能影响这种平衡关系的方法的前后,我们都可以加上assert验证:assert isBalance():"balance is destoried";
例如,在一个财会系统中,公司的支出和收入必须保持一定的平衡关系,因此我们可以编写一个表达式检查这种平衡关系,如下表示:
private boolean isBalance() {
……
}
在这个系统中,在一些可能影响这种平衡关系的方法的前后,我们都可以加上assert验证:assert isBalance():"balance is destoried";
不使用assertion的情况
•assert names.remove(null);
names.remove(null)本来是程序中的一个有效的语句,在你的算法中应该总是返回true。用断言来检查它虽然可行,但一旦断言被禁用,该语句就会被跳过。所以,无论如何都应该将断言和正确的操作分开,例如:
names.remove(null)本来是程序中的一个有效的语句,在你的算法中应该总是返回true。用断言来检查它虽然可行,但一旦断言被禁用,该语句就会被跳过。所以,无论如何都应该将断言和正确的操作分开,例如:
•boolean nullsRemoved = names.remove(null);
assert nullsRemoved;
在断言被启用的前提下,第二个语句检查返回值,第一个语句则总是执行names.remove(null)操作。这样可确保断言不会影响正常操作。
虽然可能要将断言从最终产品中剥离,但没有工具能自动删除不再需要的断言。不过,采取以下方式,也许能帮助你在编译时跳过断言语句:
assert nullsRemoved;
在断言被启用的前提下,第二个语句检查返回值,第一个语句则总是执行names.remove(null)操作。这样可确保断言不会影响正常操作。
虽然可能要将断言从最终产品中剥离,但没有工具能自动删除不再需要的断言。不过,采取以下方式,也许能帮助你在编译时跳过断言语句:
•static final boolean isAssertUsed = true;
if (isAssertUsed) assert MyCondition;
上述代码取代了单行语句assert MyCondition。如果isAssertUsed为true,它会触发断言。如果编译时不需要断言,将isAssertUsed的值变成false就可以了:
if (isAssertUsed) assert MyCondition;
上述代码取代了单行语句assert MyCondition。如果isAssertUsed为true,它会触发断言。如果编译时不需要断言,将isAssertUsed的值变成false就可以了:
•static final boolean isAssertUsed = false;
if (isAssertUsed) assert MyCondition;
由于条件isAssertUsed总是为false,所以if内部的内容是一个不可读的区域,编译器会自动跳过这个区域。这称为“条件编译”。进行上述修改并重新编译了代码后,程序的生产版本将不再含有断言。
if (isAssertUsed) assert MyCondition;
由于条件isAssertUsed总是为false,所以if内部的内容是一个不可读的区域,编译器会自动跳过这个区域。这称为“条件编译”。进行上述修改并重新编译了代码后,程序的生产版本将不再含有断言。
•还建议你在初始化一个类之前,检查断言是否启用。在断言被禁用的情况下,以下代码有助于防止一个类被初始化:
•static {
boolean isAssertEnabled = false;
assert isAssertEnabled = true;
if (!isAssertEnabled)
throw new RuntimeException("Assertion must be enabled!");
}
断言启用的前提下,assert isAssertEnabled = true语句可将变量isAssertEnabled设为true。在断言被禁用的前提下,会跳过该语句(变量依然为false)。所以,如果断言是禁用的,就会运行if语句并引发异常。
boolean isAssertEnabled = false;
assert isAssertEnabled = true;
if (!isAssertEnabled)
throw new RuntimeException("Assertion must be enabled!");
}
断言启用的前提下,assert isAssertEnabled = true语句可将变量isAssertEnabled设为true。在断言被禁用的前提下,会跳过该语句(变量依然为false)。所以,如果断言是禁用的,就会运行if语句并引发异常。