作者: 甲子
摘要
J2SE 1.4 在JAVA中新增添了assertion(暂译作断定)功能。 最简单的情形下,在JAVA代码中任一行可以写入一条布尔表达式, 在这一行代码的最前面加上assert关键字,就可以实现这样的功能: 如果表达式为真,则代码继续执行;否则,抛出异常。为了实现这项功能, 在JAVA中新添加了assert关键字,AssertionError类, java.lang.ClassLoader中增加了几个新的方法。 本文章详细介绍了assert关键字的使用, 从命令行控制assertion功能,从代码内部控制assertion功能,以及何时使用assertion功能等内容。下文中提到assert时特指assert关键字,而提到assertion则表示断定语句或断定功能。
assertion功能提供了一种在代码中进行正确性检查的机制,这种检查通常用于开发和调试阶段,到了软件完成部署后就可以关闭。这使得程序员可以在代码中加入调试检查语句,同时又可以在软件部署后关闭该功能而避免对软件速度和内存消耗的影响。基本上,assertion功能就是JAVA中的一种新的错误检查机制,只不过这项功能可以根据需要关闭。
通常在C和C++中,断定功能语句是可以通过预处理过程而不编译进最终的执行代码,由于JAVA中没有宏功能,所以在以前的java版本中断定功能没有被广泛的使用,在JDK1.4中通过增加assert关键字改变了这种状况。
这项新功能最重要的特点是断定语句可以在运行时任意的开启或关闭,这意味着这些起错误检查功能的语句不必在开发过程结束后从源代码中删除。
assertion语法非常简单,但正确的使用能帮助我们编写出健壮(ROBAST)可靠的代码。这篇文章中,我们不仅学习如何编写assertion语句,更要讨论应该在什么情况下使用assertion语句。
一、assertion语法基本知识
我们可以用新的JAVA关键字assert来书写断定语句。一条断定语句有以下两种合法的形式:
assert expression1;
assert expression1 : expression2;
expression1是一条被判断的布尔表达式,必须保证在程序执行过程中它的值一定是真;expression2是可选的,用于在expression1为假时,传递给抛出的异常AssertionError的构造器,因此expression2的类型必须是合法的AssertionError构造器的参数类型。以下是几条断定语句的例子:
assert 0 < value;
assert ref != null;
assert count == (oldCount + 1);
assert ref.m1(parm);
assert关键字后面的表达式一定要是boolean类型,否则编译时就会出错。
以下是使用断定语句的一个完整例子(见粗体语句行):
public class aClass {
public void aMethod( int value ) {
assert value = 0;
System.out.println( "OK" );
}
public static void main( String[] args ){
aClass foo = new aClass();
System.out.print( "aClass.aMethod( 1 ): " );
foo.aMethod( 1 );
System.out.print( "aClass.aMethod( -1 ): " );
foo.aMethod( -1 );
}
}
这段程序通过语句 assert value = 0; 来判断传入aMethod方法中的参数是否不小于0,如果传入一个负数,则会触发AssertionError的异常。
为了和J2SE 1.4 以前的程序兼容,在JDK1.4 中的javac 和 java 命令在默认情况下都是关闭assertion功能的,即不允许使用assert作为关键字,这就保证了如果你以前编写的程序中如果使用了assert作为变量名或是方法名,程序不必修改仍然可以运行。但需要注意的是,这些程序是无法使用JDK1.4 的javac进行重新编译的,只能使用JDK1.3或之前的版本编译。为了编译我们前面写的小程序,首先要使用符合J2SE 1.4 的编译器,同时还要使用几个命令行参数来使编译器启用assertion功能。
使用以下的命令来编译aClass.java:
javac -source 1.4 aClass.java
如果我们使用 ?java aClass 来运行这段程序,就会发现assertion语句实际上并未得到执行,和javac一样,java命令在默认情况下,关闭了assertion功能,因而会忽略assertion语句。如何启用assertion语句将在下一节讨论。
二、通过命令行控制assertion功能
assertion语句的一项最重要的特点是它可以被关闭,关闭的作用是这条代码虽然仍存在于程序当中,但程序运行时,JVM会忽略它的存在,不予执行,这样代码的运行速度不会由于assertion语句的存在而受到影响,如果代码执行过程中出现了问题,我们又可以启用assertion语句,帮助我们进行分析判断。默认情况下,这项功能是关闭的。(提示:本小节介绍的命令行参数都是针对SUN提供的JDK1.4而言,如果使用其他公司的JDK则未必会完全一样。)
JDK1.4 中,通过java命令的命令行选项 -ea (-enableassertions 的缩写)来启用。以下两个命令是等效的:
java -ea myPackage.myProgram
java -enableassertions myPackage.myProgram
同样,我们通过 -da (-disableassertions 的缩写)来关闭assertion功能:
java -da myPackage.myProgram
java -disableassertions myPackage.myProgram
assertion功能还可以针对特定的包(package)或类(class)分别启用和关闭。针对类时,使用完整的类名;针对包时,包名后面紧跟“...”:
java -ea:<class myPackage.myProgram
java -da:<package... myPackage.myProgram
在一个java命令中使用多项 -ea -da 参数时,后面的参数设定会覆盖前面参数的设定,比如我们可以默认启用所有的assertion功能,但针对特定的包却关闭此功能:
java -ea -da:<package... myPackage.myProgram
对于未命名的包(位于当前目录中)都属于默认包,可以使用以下的命令控制:
java -ea:... myPackage.myProgram
java -da:... myPackage.myProgram
对于随JVM安装时自己附带的所有系统类,可以分别使用 -esa(-enablesystemassertions)和-dsa(-disablesystemassertions)来控制assertion功能的启用和关闭。在表1.1中列出了控制assertion功能参数的所有用法。
表1 JDK1.4 中java命令和assertion功能有关的命令行参数
命令行参数 实例 含义
-ea Java -ea 启用除系统类外所有类的assertion
-da
Java -da 关闭除系统类外所有类的assertion
-ea:<classname
Java -ea:AssertionClass 启用AssertionClass类的assertion
-da:<classname
Java -da:AssertionClass 关闭AssertionClass类的assertion
-ea:<packagename Java -ea:pkg0... 启用pkg0包的assertion
-da:<packagename Java -da:pkg0... 关闭pkg0包的assertion
-esa
Java -esa 启用系统类中的assertion
-dsa
Java -dsa 关闭系统类中的assertion
至此,我们前面编写的小程序aClass可以用以下的任意命令运行:
java -ea aClass
java -ea:aClass aClass
java -ea:... aClass
运行结果如下:
aClass.aMethod( 1 ): OK
aClass.aMethod( -1 ): java.lang.AssertionError
at aClass.aMethod(aClass.java:3)
at aClass.main(aClass.java:12)
Exception in thread "main"
三、assertion命令行参数之间的继承关系
assertion功能的启用和关闭可以一直控制到每一个类,一个命令行可以容纳任意多个-ea -da 参数,这些参数之间是如何相互起作用的,基本上遵循两个原则:特定具体的设定优先于一般的设定,后面的?设定优先于前面的设定。我们看下面的例子:
// Base.java
package tmp;
public class Base{
public void m1( boolean test ){
assert test : "Assertion failed: test is " + test;
System.out.println( "OK" );
}
}
// Derived.java
//
package tmp.sub;
import tmp.Base;
public class Derived extends Base{
public void m2( boolean test ){
assert test : "Assertion failed: test is " + test;
System.out.println( "OK" );
}
public static void printAssertionError( AssertionError ae ){
StackTraceElement[] stackTraceElements = ae.getStackTrace();
StackTraceElement stackTraceElement = stackTraceElements[ 0 ];
System.err.println( "AssertionError" );
System.err.println( " class= " + stackTraceElement.getClassName() );
System.err.println( " method= " + stackTraceElement.getMethodName() );
System.err.println( " message= " + ae.getMessage() );
}
public static void main( String[] args ){
try{
Derived derived = new Derived();
System.out.print( "derived.m1( false ): " );
derived.m1( false );
System.out.print( "derived.m2( false ): " );
derived.m2( false );
}catch( AssertionError ae ){
printAssertionError( ae );
}
}
}
Base类和Derived