八、异常:如果出现问题
对于非常简单的程序来说,确保程序的所有部分都准确地做它们应该做的事情可能很容易。对于复杂程度更高的程序,那些由许多开发人员构建的程序,或者那些使用外部程序(库)的程序,情况就不那么清楚了。例如,如果列表或数组的地址越界,对文件或网络数据流的一些 I/O 访问失败,或者对象以意外或损坏的状态结束,就会出现问题。
这就是异常的用途。异常是,如果发生了意想不到的、可能是恶意的事情,对象会被创建或被抛出。然后,特殊的程序部分可以接收这样的异常对象并适当地动作。
Kotlin 和异常
Kotlin 对待异常状态的方式相当自由,但 Android 没有。如果你不关心你的应用中的异常,并且任何程序部分碰巧抛出了异常,Android 会清醒地告诉你应用崩溃了。您可以通过将可疑的程序部分放入 try-catch 块来防止这种情况:
try {
// ... statements
} catch(e:Exception) {
// do something
}
or
try {
// ... statements
} catch(e:Exception) {
// do something...
} finally {
// do this by any means: ...
}
在这两种情况下,该构造都被称为捕获异常。可选的 finally 块在构造结束时被执行,不管异常是否被捕获。你通常用它来清理try { }
中的代码可能造成的混乱,包括关闭任何打开的文件或网络资源以及类似的操作。
注意
根据经验,在代码中使用许多 try-catch 子句很难提高代码质量。别这样。不过,在应用的中心位置放几个这样的图标通常是个好主意。
一旦在try{ }
块中出现异常——这包括来自那里的任何方法调用——程序流立即分支到catch{ }
块。这是一个很难回答的问题,尤其是在 Android 环境下。当你开发一个应用时,写日志条目肯定是一个好主意。这不是 Kotlin 标准库的一部分,但是 Android 提供了一个单例对象android.util.Log
,你可以用它来写日志:
import android.util.Log
...
try {
// ... statements
} catch(e:Exception) {
Log.e("LOG", "Some exception occurred", e)
}
当然,您可以写一些更具体的信息,而不是这里显示的日志文本。
注意
如果你看一下android.util.Log
类,你会发现这是一个 Java 类,函数e()
是一个不需要实例的静态函数。因此,严格来说,它不是一个单例对象,但是从 Kotlin 的角度来看,它就像是一个单例对象。
开发应用时,您可以在 Logcat 选项卡上看到日志语句,前提是您使用的是仿真器或连接的硬件设备,并且打开了调试功能。使用来自Log
类的e()
函数提供了一个优势,你可以得到一个堆栈跟踪,这意味着行号被指出,导致错误程序部分的函数调用被列出。图 8-1 给出了一个例子。
图 8-1。
Android Studio 中的异常记录
对于您的最终用户来说,以这种方式提供日志记录是不可取的,因为在大多数情况下,您的用户不知道如何检查日志文件。您可以做的是以Toast
的形式显示一条简短的错误消息,如下所示:
import android.util.Log
...
try {
// ... statements
} catch(e:Exception) {
Log.e("LOG", "Some exception occurred", e)
Toast.makeText(this,"Error Code 36A",
Toast.LENGTH_LONG).show()
}
当然,您向用户呈现的具体内容取决于异常的严重程度。也许您可以以某种方式清除错误状态,并继续正常的程序流程。在非常严重的情况下,您可以显示一个错误消息对话框或分支到一个错误处理活动。
更多异常类型
到目前为止,我们看到的Exception
类只是一种异常。如果我们在一个catch
语句中使用Exception
,我们正式表达了一种非常一般的异常。根据具体情况,您的应用可能与只使用Exception
的 try-catch 子句共存得很好。然而,你也可以使用Exception
的许多子类。例如,有一个ArrayIndexOutOfBounds
异常,一个IllegalArgumentException
,一个IllegalStateException
,等等。通过添加更多的catch{ }
子句,您甚至可以同时使用多个:
try {
// ... statements
} catch(e:ExceptionType1) {
// do something...
} catch(e:ExceptionType2) {
// do something...
... possibly more catch statements
} finally {
// do this by any means: ...
}
如果在try{ }
中抛出一个异常,那么所有的catch
子句都会被一个接一个地检查,如果其中一个声明的异常匹配,那么相应的catch
子句就会被执行。如果你想捕捉几个异常,你通常做的是把更具体的捕捉放在列表的开始,把最一般的放在最后。例如,假设您有一些访问文件、处理数组的代码,此外还可能抛出未知的异常。你可以在这里写
try {
// ... file access
// ... array access
} catch(e:IOException) {
// do something...
} catch(e:ArrayIndexOutOfBoundsException) {
// do something...
} catch(e:Exception) {
// do something...
} finally {
// do this by any means: ...
}
这里的finally
子句是可选的,和往常一样。
自己抛出异常
从您编写的代码中引发异常
throw exceptionInstance
其中exceptionInstance
是异常类的实例,例如
val exc = Exception("The exception message")
throw exc
或者
throw Exception("The exception message")
因为异常是普通类,除了在catch
子句中的可用性,还可以定义自己的异常。只需扩展Exception
类或它的任何子类:
class MyException(msg:String) : Exception(msg)
...
try {
...
throw MyException("an error occurred")
...
} catch(e:MyException) {
...
}
练习 1
在NumberGuess
游戏 app 中,定义一个新的类GameException
作为Exception
的扩展。检查用户输入的数字,如果超过最小或最大的可猜测数字,抛出一个GameException
。在guess()
函数中捕捉新的异常,并可能显示一条Toast
消息。提示:使用if (num.text.toString().toInt() < Constants.LOWER_BOUND) throw ...
和if (num.text.toString().toInt() > Constants.UPPER_BOUND) throw ...
进行检查。
表达式中的异常
Kotlin 的一个有趣特性是,可以在表达式中使用 try-catch 块和 throw 语句。try-catch 块的结果是try{ }
或catch(...){ }
块中最后一行的值,这取决于异常是否被捕获。例如,如果出现问题,您可以将它用作默认值。在…里
val x = try{ arr[ind] }
catch(e:ArrayIndexOutOfBoundsException) { -1 }
对于一些名为arr
的IntArray
,如果违反了数组边界限制,变量x
将获得默认值1
。
警告
注意不要滥用 try-catch 块来处理一些异常的但却是预期的程序流路径。你真的应该只对意料之外的问题使用异常。
一个throw someException
也有一个值。它属于Nothing
类型,在 Kotlin 类型层次结构中是所有事物的子类。因此有可能写
val v = map[someKey] ?: throw Exception("no such key in the map")
注意算子?:
(有时被称为埃尔维斯算子)只有在左侧产生null
时才对右侧求值;否则它走左边。这意味着如果map[someKey]
的值为null
,相当于地图没有这个键,那么就会抛出异常。
版权属于:月萌API www.moonapi.com,转载请注明出处