Kotlin 的異常處理機(jī)制主要依賴于try、catch、finally、throw 這四個(gè)關(guān)鍵字类垦,其中try關(guān)鍵字后緊跟一個(gè)用花括號(hào)括起來的代碼塊(花括號(hào)不可省略),簡(jiǎn)稱try塊坦弟,它里面放置可能引發(fā)異常的代碼护锤。 catch 后對(duì)應(yīng)異常類型和一個(gè)代碼塊,表明該 catch 塊用于處理這種類型的代碼塊酿傍。 多個(gè)catch塊后還可以跟一個(gè) finally塊,用于回收在try塊里打開的物理資源驱入,異常處理機(jī)制會(huì)保證 finally塊總被執(zhí)行赤炒。 而 throw用于拋出一個(gè)實(shí)際的異常, throw可以單獨(dú)作為語句使用亏较,拋出一個(gè)具體的異常對(duì)象 莺褒。
與 Java 的異常處理機(jī)制相比, Kotlin 拋棄了checked 異常雪情,相當(dāng)于所有異常都是 runtime 異常遵岩,這意味著開發(fā)者想捕獲異常就捕獲,不想捕獲異常也行,不需要使用 throws 關(guān)鍵字聲明拋出異常尘执。
異常處理機(jī)制
使用 try...catch 捕獲異常
Kotlin 的異常處理機(jī)制的語法結(jié)構(gòu)如下:
try{
//業(yè)務(wù)實(shí)現(xiàn)代碼
...
}catch(e:Exception){
//異常處理代碼
...
}
...
finally{
//可選的finally 塊
}
與Java的異常處理流程相同舍哄,整個(gè)異常處理流程可包含 1個(gè)try塊、0~N個(gè)catch塊誊锭、 0~1個(gè) finally塊表悬,但 catch塊與 finally塊至少出現(xiàn)其中之一。
如果在執(zhí)行try塊中的業(yè)務(wù)邏輯代碼時(shí)出現(xiàn)異常丧靡,系統(tǒng)將自動(dòng)生成一個(gè)異常對(duì)象蟆沫,該異常對(duì)象被提交給運(yùn)行時(shí)環(huán)境,這個(gè)過程被稱為拋出( throw)異常温治。
當(dāng)運(yùn)行時(shí)環(huán)境收到異常對(duì)象時(shí)饭庞,會(huì)尋找能處理該異常對(duì)象的 catch塊,如果找到了合適的catch塊熬荆,就把該異常對(duì)象交給該 catch 塊處理但绕,這個(gè)過程被稱為捕獲( catch)異常;如果運(yùn)行時(shí)環(huán)境找不到捕獲異常的catch塊,則運(yùn)行時(shí)環(huán)境中止惶看,程序也將退出 捏顺。
import java.io.FileInputStream
import java.io.IOException
fun main(args: Array<String>) {
var fis:FileInputStream? = null
try{
fis = FileInputStream("a.txt")
println(fis.read())
}catch (e:IOException){
println("讀取文件出現(xiàn)異常")
//return 語句強(qiáng)制方法返回
//return
//使用 exit 退出虛擬機(jī)
//System.exit(1)
}finally {
//關(guān)閉磁盤文件,回收資源
fis?.close()
println("執(zhí)行 finally 塊里的資源回收!")
}
}
上面程序在try塊后增加了 finally 塊纬黎,用于回收在try塊中打開的物理資源 幅骄。 注意程序的 catch塊中有一條return語句,該語句強(qiáng)制方法返回本今。 在通常情況下拆座, 一旦在方法中執(zhí)行到return語句的地方, 程序?qū)⒘⒓唇Y(jié)束該方法; 而現(xiàn)在不會(huì)了冠息,雖然return語句也強(qiáng)制方法結(jié)束挪凑,但一定會(huì)先執(zhí)行 finally塊中的代碼。
在異常處理的 catch 塊中使用 System.exit(1)語句來退出虛擬機(jī)逛艰。運(yùn)行上面代碼躏碳,可以發(fā)現(xiàn)finally塊沒有被執(zhí)行。 如果在異常處理代碼中使用 System.exit(1)語 句來退出虛擬機(jī)散怖,則finally塊將失去執(zhí)行的機(jī)會(huì)菇绵。
當(dāng)程序執(zhí)行try塊、catch 塊時(shí)遇到了 return 或 throw 語句镇眷,這兩條語句都會(huì)導(dǎo)致該方法立即結(jié)束咬最, 但是系統(tǒng)執(zhí)行這兩條語句并不會(huì)結(jié)束該方法, 而是去尋找該異常處理流程中是否包含finally塊欠动,如果沒有finally塊永乌,程序?qū)⒘⒓磮?zhí)行return或 throw語句,方法中止;如果有finally塊,系統(tǒng)就立即開始執(zhí)行 finally 塊翅雏,只有當(dāng) finally 塊執(zhí)行完成后圈驼,系統(tǒng)才會(huì)再次跳回來執(zhí)行try塊、catch 塊中的 return 或 throw 語句 : 如果 finally塊中也使用了return 或 throw 等導(dǎo)致方法中止的語句枚荣, finally塊己經(jīng)中止了方法碗脊,系統(tǒng)將不會(huì)跳回去執(zhí)行try 塊、catch塊中的任何代碼橄妆。
異常類的繼承體系
與Java 的異常處理流程類似衙伶,當(dāng)運(yùn)行時(shí)環(huán)境接收到異常對(duì)象后,會(huì)依次判斷該異常對(duì)象是否是 catch 塊后異常類或其子類的實(shí)例害碾,如果是矢劲,運(yùn)行時(shí)環(huán)境將調(diào)用該 catch塊來處理該異常;否則將再次拿該異常對(duì)象和下一個(gè) catch塊中的異常類進(jìn)行比較。
當(dāng)程序進(jìn)入負(fù)責(zé)異常處理的 catch塊時(shí)慌随,系統(tǒng)生成的異常對(duì)象 ex 將會(huì)傳給 catch 塊后的異常形參芬沉,從而允許 catch 塊通過該對(duì)象來獲得異常的詳細(xì)信息 。
Kotlin 提供了 kotlin.Throwable 類阁猜,所有的異常類都是 Throwable 類的子類丸逸。此外,Kotlin 還通過類型別名的方式引入了 Java 的 Error和 Exception 兩個(gè)子類剃袍,但 Kotlin 并沒有嚴(yán)格區(qū)分錯(cuò)誤和異常(在 Java 中 Error 和 Exception 的區(qū)別也不明顯) 黄刚。 此外, Kotlin 完全借用了 Java 的異常體系民效,這些異常類之間有嚴(yán)格的繼承關(guān)系 憔维。
下面看 一個(gè)異常捕獲的例子:
fun main(args: Array<String>) {
try {
var a = Integer.parseInt(args[0])
var b = Integer.parseInt(args[1])
val c = a / b
println(" 您輸入的兩個(gè)數(shù)相除的結(jié)果是 : ${c} ")
} catch (ie: IndexOutOfBoundsException) {
println(" 數(shù)組越界 : 運(yùn)行程序時(shí)輸入 的參數(shù)個(gè)數(shù)不夠 ")
} catch (ne: NumberFormatException) {
println(" 數(shù)字格式異常 : 程序只能接收整數(shù)參數(shù) ")
} catch (ae: ArithmeticException) {
println(" 算術(shù)異常 ")
} catch (e: Exception) {
println(" 未知異常 ")
}
}
訪問異常信息
如果程序需要在 catch 塊中訪問異常對(duì)象的相關(guān)信息,則可以通過訪問 catch 塊后的異常形參來獲得畏邢。當(dāng)運(yùn)行時(shí)環(huán)境決定調(diào)用某個(gè) catch 塊來處理該異常對(duì)象時(shí)业扒,會(huì)將異常對(duì)象賦給catch塊后的異常參數(shù),程序即可通過該參數(shù)來獲得異常的相關(guān)信息 舒萎。
所有的異常對(duì)象都包含了如下幾個(gè)常用屬性和方法程储。
- message: 該屬性返回該異常的詳細(xì)描述字符串。
- stackTrace: 該屬性返回該異常的跟蹤棧信息逆甜。
- printStackTrace(): 將該異常的跟蹤棧信息輸出到標(biāo)準(zhǔn)錯(cuò)誤輸出虱肄。
- printStackTrace(PrintStream s): 將該異常的跟蹤棧信息輸出到指定輸出流。
try語句是表達(dá)式
與if語句類似交煞, Kotlin 的try語句也是表達(dá)式,因此位try語句也可用于對(duì)變量賦值斟或。 try表達(dá)式的返回值是try塊中的最后一個(gè)表達(dá)式的值素征,或者是被執(zhí)行的 catch 塊中的最后一個(gè)表達(dá)式的值, finally 塊中的內(nèi)容不會(huì)影響表達(dá)式的結(jié)果。
如下程序示范了try語句是一個(gè)表達(dá)式的效果御毅。
fun main(args: Array<String>) {
val input = readLine()
//用 try 表達(dá)式對(duì)變量 a 賦值
val a:Int? = try{Integer.parseInt(input)}catch (e: NumberFormatException){null}finally {
"sss"
}
println(a)
}
使用 throw拋出異常
與Java類似根欧,Kotlin也允許程序自行拋出異常,自行拋出異常使用 throw語句來完成端蛆。
拋出異常
如果需要在程序中自行拋出異常凤粗,則應(yīng)使用 throw 語句。throw 語句可以單獨(dú)使用今豆,throw 語句拋出的不是異常類嫌拣,而是一個(gè)異常實(shí)例,而且每次只能拋出一個(gè)異常實(shí)例呆躲。 throw語句的語法格式如下:
- throw Exceptioninstance
由于 Kotlin 沒有 checked 異常(即使某個(gè)異常在 Java 中原本是 checked 異常异逐,在 Kotlin 中它也不是 checked 異常),因此 Kotlin 拋出異常的語句無須放在 try 塊中插掂,程序既可以顯式使用 try...catch 來捕獲井處理該異常灰瞻,也可以完全不理會(huì)該異常,把該異常交給該方法調(diào)用者處理辅甥。例如下面程序:
fun main(args: Array<String>) {
//無論該異常在 Java 中是否為 checked 異常
// 在 Kotlin 中該異常都不是 checked 異常
throwChecked(-5)
throwRuntime(4)
}
fun throwChecked(a:Int){
if(a>0){
//自行拋出普通異常酝润,在 Kotlin 中 也不 是 checked 異常
//該代碼不必處于 try 塊中
throw Exception("a的值大于 0,不符合要求")
}
}
fun throwRuntime(a:Int){
if(a>0){
throw RuntimeException("a的值大于 0璃弄,不符合要求")
}
}
上面代碼分別拋出了 Exception 和 RuntimeException 異常要销, Exception 在 Java 中就是checked 異常 , 但在 Kotlin 中不是谢揪,所以我們看到上面兩個(gè)方法對(duì)兩種不同異常的處理完全相同蕉陋。
自定義異常類
在通常情況下,程序很少會(huì)自行拋出系統(tǒng)異常拨扶,因?yàn)楫惓n惖念惷ǔR舶嗽摦惓5挠杏眯畔?凳鬓。所以在選擇拋出異常時(shí),應(yīng)該選擇合適的異常類患民,從而可以明確地描述該異常情況缩举。在這種情形下,應(yīng)用程序常常需要拋出自定義異常 匹颤。
用戶自定義異常都應(yīng)該繼承 Exception 基類仅孩,定義異常類時(shí)通常需要提供兩個(gè)構(gòu)造器: 一個(gè)是無參數(shù)的構(gòu)造器;另一個(gè)是帶一個(gè)字符串參數(shù)的構(gòu)造器,這個(gè)字符串將作為該異常對(duì)象的描述信息(也就是異常對(duì)象的message屬性的返回值) 印蓖。
下面例子程序創(chuàng)建了一個(gè)自定義異常類辽慕。
class AuctionException : Exception {
//無參數(shù)的構(gòu)造器
constructor() {
}
//帶一個(gè)字符串參數(shù)的構(gòu)造器
constructor(msg: String) : super(msg) {
}
}
上面程序創(chuàng)建了 AuctionException 異常類 , 井為該異常類提供了兩個(gè)構(gòu)造器 赦肃。 尤其是帶一個(gè)字符串參數(shù)的構(gòu)造器溅蛉,該構(gòu)造器通過 super 委托調(diào)用父類的構(gòu)造器公浪, 正是這個(gè)super調(diào)用可以將此字符串參數(shù)傳給異常對(duì)象的message屬性, 該 message屬性就是該異常對(duì)象的詳細(xì)描述信息 船侧。
在大部分情況下欠气,創(chuàng)建自定義異常都可采用與 AuctionException.kt相似的代碼完成,只需改變 AuctionException異常類的類名即可镜撩,讓該異常類的類名可以準(zhǔn)確描述該異常预柒。
catch 和 throw 同時(shí)使用
實(shí)際應(yīng)用的異常處理可能需要更復(fù)雜的處理方式,當(dāng)一個(gè)異常出現(xiàn)時(shí)袁梗,單靠某個(gè)方法無法完全處理該異常宜鸯,必須通過幾個(gè)方法協(xié)作才可完全處理該異常。也就是說围段,在異常出現(xiàn)的當(dāng)前方法中顾翼,程序只對(duì)異常進(jìn)行部分處理,還有些處理需要在該方法的調(diào)用者中才能完成 奈泪,所以應(yīng)該再次拋出異常适贸,讓該方法的調(diào)用者也能捕獲到異常。
為了實(shí)現(xiàn)這種通過多個(gè)方法協(xié)作處理同一個(gè)異常的情形涝桅,可以在 catch塊中結(jié)合 throw語句來完成拜姿。如下例子程序示范了這種 catch 和 throw 同時(shí)使用的方法。
class AuctionTest {
var initPrice: Double = 30.0
fun bid(bidPrice: String) {
var d: Double =0.0
try {
bidPrice.toDouble()
} catch (e: Exception) {
//此處完成本方法中可以對(duì)異常執(zhí)行的修復(fù)處理
//此處僅僅是在控制臺(tái)打印異常的跟蹤棧信息
e.printStackTrace()
//再次拋出自定義異常
throw AuctionException("”競(jìng)拍價(jià)必須是數(shù)值冯遂,不能包含其他字符!”")
}
if (initPrice > d) {
throw AuctionException("競(jìng)拍價(jià)比起拍價(jià)低蕊肥,不允許競(jìng)拍! ")
}
initPrice = d
}
}
fun main(args: Array<String>) {
val at = AuctionTest()
try {
at.bid("ed")
} catch (ae: AuctionException) {
//再次捕獲到 bid ()方法中的異常,并對(duì)該異常進(jìn)行處理
println(ae.message)
}
}
這種 catch 和 throw 結(jié)合使用的情況在大型企業(yè)級(jí)應(yīng)用中非常常用。企業(yè)級(jí)應(yīng)用對(duì)異常的處理通常分成兩個(gè)部分:1 后臺(tái)需要通過日志來記錄異常發(fā)生的詳細(xì)情況:2 應(yīng)用還需要根據(jù)異常向應(yīng)用使用者傳達(dá)某種提示蛤肌。在這種情形下壁却,所有異常都需要兩個(gè)方法共同完成,也就必須結(jié)合使用 catch和 throw裸准。
throw語句是表達(dá)式
與 try語句是表達(dá)式一樣展东, Kotlin 的throw語句也是表達(dá)式,但由于throw表達(dá)式的類型比較特殊炒俱,是 Nothing 類型盐肃,因此很少將 throw 語句賦值給其他變量,但我們可以在Elvis表達(dá)式中使用 throw 表達(dá)式权悟。例如如下代碼:
class User(var name:String? = null,var password:String? =null){
}
fun main(args: Array<String>) {
val user =User()
//在 Elvis 表達(dá)式中使用 throw 表達(dá)式
//throw表達(dá)式表示程序出現(xiàn)異常砸王,不會(huì)真正對(duì)變量賦值
val th :String = user.name ?: throw NullPointerException("”目標(biāo)對(duì)象不能為 null")
println(th)
}
上面代碼將user.name賦值給th變量,由于user.name 是可空類型峦阁,因此程序?qū)?user.name使用了Elvis表達(dá)式進(jìn)行判斷:當(dāng) user.name不為null時(shí)谦铃,將其值賦給user.name; 否則使用 throw表達(dá)式的值 , throw表達(dá)式的類型是 Nothing榔昔, 用于表示程序無法“真正”得 到該表達(dá)式的值荷辕。因此凿跳,如果 user.name為 null件豌,程序?qū)?huì)出現(xiàn)異常疮方,不會(huì)對(duì) th變量賦值, 故可將 th變量聲明為String類型茧彤。
編譯骡显、運(yùn)行該程序,可看到程序因?yàn)?NullPointerException 異常而結(jié)束 曾掂。
Nothing 類型沒有值惫谤,而是用于標(biāo)記永遠(yuǎn)無法“真正”返回的表達(dá)式,因此程序不會(huì)獲取表達(dá)式的值 珠洗。 當(dāng)我們自己定義函數(shù)時(shí) 溜歪,也可使用 Nothing 來標(biāo)記一個(gè)永遠(yuǎn)不會(huì)返回的函數(shù)。
fun fail(message: String): Nothing {
throw IllegalArgumentException(message)
}
異常的跟蹤棧
異常對(duì)象的printStackTrace()方法用于打印異常的跟蹤棧信息许蓖,根據(jù)printStackTrace()方法的輸出結(jié)果蝴猪,開發(fā)者可以找到異常的源頭,并跟蹤到異常一路觸發(fā)的過程:
class SelfException : Exception {
constructor() {
}
constructor(msg: String) : super(msg) {
}
}
class PrintStackTraceTest{
fun firstMethod(){
secondMethod()
}
fun secondMethod(){
thirdMethod()
}
fun thirdMethod(){
throw SelfException("自定義異常信息")
}
}
fun main(args: Array<String>) {
PrintStackTraceTest().firstMethod()
}
如圖: