assert expression1;
assert expression1:expression2;
如果expression1為true,則不拋出錯(cuò)誤,程序正常運(yùn)行,expression2也不會(huì)執(zhí)行。
如果expression1為false,則拋出異常,程序中斷跳出,expression2執(zhí)行。
一般來(lái)說(shuō),不要在expression1、expression2中使用函數(shù)的返回值;
不要將其使用在public函數(shù)中檢查輸入?yún)?shù),但可以用于private函數(shù)中檢測(cè)輸入?yún)?shù)。
使用時(shí),編譯時(shí)需要用javac -source;執(zhí)行時(shí)需要使用java -ea。
個(gè)人的一點(diǎn)理解:使用assert只是為了幫助我們調(diào)試程序,因此使用assert所遵循的原則就是“不能因?yàn)橛辛薬ssert的存在而使程序的結(jié)構(gòu)發(fā)生任何的改變”,說(shuō)白了就是“如果把a(bǔ)ssert部分刪除了,程序依然不會(huì)有任何的問題,只不過(guò)不能幫助我們檢查出一些錯(cuò)誤來(lái)了”,因此使用assert的時(shí)候不應(yīng)該在表達(dá)式中使用函數(shù),因?yàn)橐坏┌堰@句assert語(yǔ)句刪除后,程序的結(jié)構(gòu)就改變了,這不符合上述提到的原則!
附上一篇寫得很不錯(cuò)的原文:
深入解析Java的assertion
一、assertion的語(yǔ)法和語(yǔ)義
J2SE 1.4在語(yǔ)言上提供了一個(gè)新特性,就是assertion(斷言)功能,它是該版本在Java語(yǔ)言方面最大的革新。在軟件開發(fā)中,assertion是一種經(jīng)典的調(diào)試、測(cè)試方式,本文將深入解析assertion功能的使用以及其設(shè)計(jì)理念,并給出相關(guān)的例子 。
assertion(斷言)在軟件開發(fā)中是一種常用的調(diào)試方式,很多開發(fā)語(yǔ)言中都支持這種機(jī)制,如C,C++和Eiffel等,但是支持的形式不盡相同,有的是通過(guò)語(yǔ)言本身、有的是通過(guò)庫(kù)函數(shù)等。另外,從理論上來(lái)說(shuō),通過(guò)assertion方式可以證明程序的正確性,但是這是一項(xiàng)相當(dāng)復(fù)雜的工作,目前還沒有太多的實(shí)踐意義。
在實(shí)現(xiàn)中,assertion就是在程序中的一條語(yǔ)句,它對(duì)一個(gè)boolean表達(dá)式進(jìn)行檢查,一個(gè)正確程序必須保證這個(gè)boolean表達(dá)式的值為true;如果該值為false,說(shuō)明程序已經(jīng)處于不正確的狀態(tài)下,系統(tǒng)將給出警告或退出。一般來(lái)說(shuō),assertion用于保證程序最基本、關(guān)鍵的正確性。assertion檢查通常在開發(fā)和測(cè)試時(shí)開啟。為了提高性能,在軟件發(fā)布后,assertion檢查通常是關(guān)閉的。下面簡(jiǎn)單介紹一下Java中assertion的實(shí)現(xiàn)。
1.1) 語(yǔ)法表示
在語(yǔ)法上,為了支持assertion,Java增加了一個(gè)關(guān)鍵字assert。它包括兩種表達(dá)式,分別如下:
- assert expression1;
- assert expression1: expression2;
在兩種表達(dá)式中,expression1表示一個(gè)boolean表達(dá)式,expression2表示一個(gè)基本類型或者是一個(gè)對(duì)象(Object) ,基本類型包括boolean,char,double,float,int和long。由于所有類都為Object的子類,因此這個(gè)參數(shù)可以用于所有對(duì)象。
1.2) 語(yǔ)義含義
在運(yùn)行時(shí),如果關(guān)閉了assertion功能,這些語(yǔ)句將不起任何作用。如果打開了assertion功能,那么expression1的值將被計(jì)算,如果它的值為false,該語(yǔ)句強(qiáng)拋出一個(gè)AssertionError對(duì)象。如果assertion語(yǔ)句包括expression2參數(shù),程序?qū)⒂?jì)算出expression2的結(jié)果,然后將這個(gè)結(jié)果作為AssertionError的構(gòu)造函數(shù)的參數(shù),來(lái)創(chuàng)建AssertionError對(duì)象,并拋出該對(duì)象;如果expression1值為true,expression2將不被計(jì)算。
一種特殊情況是,如果在計(jì)算表達(dá)式時(shí),表達(dá)式本身拋出Exception,那么assert將停止運(yùn)行,而拋出這個(gè)Exception。
1.3) 一些assertion例子
下面是一些Assert的例子。
- assert 0 < value;
- assert 0 < value:"value="+value;
- assert ref != null:"ref doesn't equal null";
- assert isBalanced();
1.4) 編譯
由于assert是一個(gè)新關(guān)鍵字,使用老版本的JDK是無(wú)法編譯帶有assert的源程序。因此,我們必須使用JDK1.4(或者更新)的Java編譯器,在使用Javac命令時(shí),我們必須加上-source 1.4作為參數(shù)。-source 1.4表示使用JDK 1.4版本的方式來(lái)編譯源代碼,否則編譯就不能通過(guò),因?yàn)槿笔〉腏avac編譯器使用JDK1.3的語(yǔ)法規(guī)則。
一個(gè)簡(jiǎn)單的例子如下:
javac -source 1.4 test.java
1.5) 運(yùn)行
由于帶有assert語(yǔ)句的程序運(yùn)行時(shí),使用了新的ClassLoader和Class類,因此,這種程序必須在JDK1.4(或者更高版本)的JRE下運(yùn)行,而不能在老版本的JRE下運(yùn)行。
由于我們可以選擇開啟assertion功能,或者不開啟,另外我們還可以開啟一部分類或包的assertion功能,所以運(yùn)行選項(xiàng)變得有些復(fù)雜。通過(guò)這些選項(xiàng),我們可以過(guò)濾所有我們不關(guān)心的類,只選擇我們關(guān)心的類或包來(lái)觀察。下面介紹兩類參數(shù):
- 參數(shù) -esa 和 -dsa:
它們含義為開啟(關(guān)閉)系統(tǒng)類的assertion功能。由于新版本的Java的系統(tǒng)類中,也使了assertion語(yǔ)句,因此如果用戶需要觀察它們的運(yùn)行情況,就需要打開系統(tǒng)類的assertion功能 ,我們可使用-esa參數(shù)打開,使用 -dsa參數(shù)關(guān)閉。
-esa和-dsa的全名為-enablesystemassertions和-disenablesystemassertions,全名和縮寫名有同樣的功能。
- 參數(shù) -ea和-ea:
它們含義為開啟(關(guān)閉)用戶類的assertion功能:通過(guò)這個(gè)參數(shù),用戶可以打開某些類或包的assertion功能,同樣用戶也可以關(guān)閉某些類和包的assertion功能。打開assertion功能參數(shù)為-ea;如果不帶任何參數(shù),表示打開所有用戶類;如果帶有包名稱或者類名稱,表示打開這些類或包;如果包名稱后面跟有三個(gè)點(diǎn),代表這個(gè)包及其子包;如果只有三個(gè)點(diǎn),代表無(wú)名包。關(guān)閉assertion功能參數(shù)為-da,使用方法與-ea類似。
-ea和-da的全名為-enableassertions和-disenableassertions,全名和縮寫名有同樣的功能。
下面表格表示了參數(shù)及其含義,并有例子說(shuō)明如何使用。
參數(shù) |
例子 |
說(shuō)明 |
-ea |
java -ea |
打開所有用戶類的assertion |
-da |
java -da |
關(guān)閉所有用戶類的assertion |
-ea:<classname> |
java -ea:MyClass1 |
打開MyClass1的assertion |
-da:<classname> |
java -da: MyClass1 |
關(guān)閉MyClass1的assertion |
-ea:<packagename> |
java -ea:pkg1 |
打開pkg1包的assertion |
-da:<packagename> |
java -da:pkg1 |
關(guān)閉pkg1包的assertion |
-ea:... |
java -ea:... |
打開缺省包(無(wú)名包)的assertion |
-da:... |
java -da:... |
關(guān)閉缺省包(無(wú)名包)的assertion |
-ea:<packagename>... |
java -ea:pkg1... |
打開pkg1包和其子包的assertion |
-da:<packagename>... |
java -da:pkg1... |
關(guān)閉pkg1包和其子包的assertion |
-esa |
java -esa |
打開系統(tǒng)類的assertion |
-dsa |
java -dsa |
關(guān)閉系統(tǒng)類的assertion |
綜合使用 |
java -dsa:MyClass1:pkg1 |
關(guān)閉MyClass1和pkg1包的assertion |
其中...代表,此包和其子包的含義。例如我們有兩個(gè)包為pkg1和pkg1.subpkg。那么pkg1...就代表pkg1和pkg1.subpkg兩個(gè)包。
另外,Java為了讓程序也能夠動(dòng)態(tài)開啟和關(guān)閉某些類和包的assertion功能,Java修該了Class和ClassLoader的實(shí)現(xiàn),增加了幾個(gè)用于操作assert的API。下面簡(jiǎn)單說(shuō)明一下幾個(gè)API的作用。
ClassLoader類中的幾個(gè)相關(guān)的API:
setDefaultAssertionStatus:用于開啟/關(guān)閉assertion功能
setPackageAssertionStatus:用于開啟/關(guān)閉某些包的assertion功能
setClassAssertionStatus: 用于開啟/關(guān)閉某些類的assertion功能
clearAssertionStatus:用于關(guān)閉assertion功能
二、assertion的設(shè)計(jì)問題
首先,我們認(rèn)為assertion是必要的。因?yàn)椋绻麤]有統(tǒng)一的assertion機(jī)制,Java程序通常使用if-then-else或者switch-case語(yǔ)句進(jìn)行assertion檢查,而且檢查的數(shù)據(jù)類型也不完全相同。assertion機(jī)制讓Java程序員用統(tǒng)一的方式處理assertion問題,而不是按自己的方式處理。另外,如果用戶使用自己的方式進(jìn)行檢查,那么這些代碼在發(fā)布以后仍然將起作用,這可能會(huì)影響程序的性能。而從語(yǔ)言言層次支持assertion功能,這將把a(bǔ)ssertion對(duì)性能帶來(lái)的負(fù)面影響降到最小。
Java是通過(guò)增強(qiáng)一個(gè)關(guān)鍵字assert實(shí)現(xiàn)支持assertion,而不是使用一個(gè)庫(kù)函數(shù)支持,這說(shuō)明Java認(rèn)為assertion對(duì)于語(yǔ)言本身來(lái)說(shuō)是非常重要的。實(shí)際上,在Java的早期的規(guī)范中,Java是能夠支持assert的,但是由于一些實(shí)現(xiàn)的限制,這些特性從規(guī)范中除去了。因此,assert的再次引入應(yīng)該是恢復(fù)了Java對(duì)assert的支持。C語(yǔ)言就是通過(guò)Assert.h函數(shù)庫(kù)實(shí)現(xiàn)斷言的支持。
Java的assertion的開啟也和C語(yǔ)言不太一樣,我們都知道在C語(yǔ)言中,assertion的開啟是在編譯時(shí)候決定的。當(dāng)我們使用debug方式編譯程序時(shí)候,assertion被開啟,而使用release方式編譯時(shí)候,assertion自動(dòng)被關(guān)閉。而Java的assertion卻是在運(yùn)行的時(shí)候進(jìn)行決定的。其實(shí),這兩種方式是各有優(yōu)缺點(diǎn)。如果采用編譯時(shí)決定方式,開發(fā)人員將處理兩種類型的目標(biāo)碼,debug版本和release版本,這加大了文檔管理的難度,但是提高了代碼的運(yùn)行效率。Java采用運(yùn)行時(shí)決定的方式,這樣所有的assertion信息將置于目標(biāo)代碼中,同一目標(biāo)代碼可以選擇不同方式運(yùn)行,增強(qiáng)目標(biāo)代碼的靈活性,但是它將犧牲因?yàn)閍ssertion而引起一部分性能損失。Java專家小組認(rèn)為,所犧牲的性能相當(dāng)小,因此java采用了運(yùn)行時(shí)決定方式。
另外,我們注意到AssertionError作為Error的一個(gè)子類,而不是RuntimeException。關(guān)于這一點(diǎn),專家組也進(jìn)行了長(zhǎng)期的討論。Error代表一些異常的錯(cuò)誤,通常是不可以恢復(fù)的,而RuntimeException強(qiáng)調(diào)該錯(cuò)誤在運(yùn)行時(shí)才發(fā)生的特點(diǎn)。AssertionError通常為非常關(guān)鍵的錯(cuò)誤,這些錯(cuò)誤往往是不容易恢復(fù)的,而且assertion機(jī)制也不鼓勵(lì)程序員對(duì)這種錯(cuò)誤進(jìn)行恢復(fù)。因此,為了強(qiáng)調(diào)assertion的含義,Java專家小組選擇了讓AssertError為Error的子類。
三、assertion與繼承
在本節(jié),我們將考慮assertion與繼承的關(guān)系,研究assert是如何定位的。如果開啟一個(gè)子類的assertion,那么它的父類的assertion是否執(zhí)行?
下面的例子將顯示如果一個(gè)assert語(yǔ)句在父類,而當(dāng)它的子類調(diào)用它時(shí),該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);
}
}
}
運(yùn)行命令 |
含義 |
結(jié)果 |
Java Derived |
不啟用assertion |
Base Method
Derived Method |
Java -ea Derived |
開啟所有assertion |
Java.lang.AssertionError:Assertion Failed:This is base |
Java -da Derived |
關(guān)閉所有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 |
從這個(gè)例子我們可以看出,父類的assert語(yǔ)句將只有在父類的assert開啟才起作用,如果僅僅開啟子類的assert,父類的assert仍然不運(yùn)行。例如,我們執(zhí)行java -ea:Derived Derived的時(shí)候,Base類的assert語(yǔ)句并不執(zhí)行。因此,我們可以認(rèn)為,assert語(yǔ)句不具有繼承功能。
四、assertion的使用
assertion的使用是一個(gè)復(fù)雜的問題,因?yàn)檫@將涉及到程序的風(fēng)格,assertion運(yùn)用的目標(biāo),程序的性質(zhì)等問題。通常來(lái)說(shuō),assertion用于檢查一些關(guān)鍵的值,并且這些值對(duì)整個(gè)程序,或者局部功能的完成有很大的影響,并且這種錯(cuò)誤不容易恢復(fù)的。assertion表達(dá)式應(yīng)該短小、易懂,如果需要評(píng)估復(fù)雜的表達(dá)式,應(yīng)該使用函數(shù)計(jì)算。以下是一些使用assertion的情況的例子,這些方式可以讓java程序的可靠性更高。
- 檢查控制流; 在if-then-else和swith-case語(yǔ)句中,我們可以在不應(yīng)該發(fā)生的控制支流上加上assert false語(yǔ)句。如果這種情況發(fā)生了,assert能夠檢查出來(lái)。
例如:x取值只能使1,2,3,我們的程序可以如下表示
switch (x)
{ case 1: …;
case 2: …;
case 3: …
default: assert false:"x value is invalid: "+x;
}
- 在私有函數(shù)計(jì)算前,檢查輸入?yún)?shù)是否有效;對(duì)于一私有些函數(shù),要求輸入滿足一些特定的條件,那么我們可以在函數(shù)開始處使用assert進(jìn)行參數(shù)檢查。對(duì)于公共函數(shù),我們通常不使用assertion檢查,因?yàn)橐话銇?lái)說(shuō),公共函數(shù)必須對(duì)無(wú)效的參數(shù)進(jìn)行檢查和處理。而私有函數(shù)往往是直接使用的。
例如:某函數(shù)可能要求輸入的參數(shù)必須不為null。那么我們可以在函數(shù)的一開始加上 assert parameter1!=null : "paramerter is null in test method";
- 在函數(shù)計(jì)算后,檢查函數(shù)結(jié)果是否有效;對(duì)于一些計(jì)算函數(shù),函數(shù)運(yùn)行完成后,某些值需要保證一定的性質(zhì),因此我們可以通過(guò)assert檢查該值。
例如,我們有一個(gè)計(jì)算絕對(duì)值的函數(shù),那么我們就可以在函數(shù)的結(jié)果處,加上一個(gè)語(yǔ)句:
assert value>=0:"Value should be bigger than 0:"+value;
通過(guò)這種方式,我們可以對(duì)函數(shù)計(jì)算完的結(jié)果進(jìn)行檢查。
- 檢查程序不變量;有些程序中,存在一些不變量,在程序的運(yùn)行生命周期,這些不變量的值都是不變的。這些不變量可能是一個(gè)簡(jiǎn)單表達(dá)式,也可能是一個(gè)復(fù)雜的表達(dá)式。對(duì)于一些關(guān)鍵的不變量,我們可以通過(guò)assert進(jìn)行檢查。
例如,在一個(gè)財(cái)會(huì)系統(tǒng)中,公司的支出和收入必須保持一定的平衡關(guān)系,因此我們可以編寫一個(gè)表達(dá)式檢查這種平衡關(guān)系,如下表示。
private boolean isBalance() {
……
}
在這個(gè)系統(tǒng)中,在一些可能影響這種平衡關(guān)系的方法的前后,我們都可以加上assert驗(yàn)證:assert isBalance():"balance is destoried";
五、結(jié)論
assertion為開發(fā)人員提供了一種靈活地調(diào)試和測(cè)試機(jī)制,它的使用也非常簡(jiǎn)單、方便。但是,如何規(guī)范、系統(tǒng)地使用assertion(特別是在Java語(yǔ)言中)仍然是一個(gè)亟待研究的問題。