上篇文章介绍了如何自定义异常,接下来我们学习如何抛出和处理异常。
- 由于异常是 class 类型,只需要按 class 对象的构建方式去创建异常即可。如表达式 FatherException() 即创建了一个类型为 FatherException 的异常。
- 仓颉语言提供 throw 关键字,用于抛出异常。用 throw 来抛出异常时,throw 之后的表达式必须是 Exception 的子类型(同为异常的 Error 不可以手动 throw ),如 throw ArithmeticException("I am an Exception!") (被执行到时)会抛出一个算术运算异常。
- throw 关键字抛出的异常需要被捕获处理。若异常没有被捕获,则由系统调用默认的异常处理函数。
异常处理由 try 表达式完成,可分为:
- 不涉及资源自动管理的普通 try 表达式;
- 会进行资源自动管理 try-with-resources 表达式。
普通 try 表达式
普通 try 表达式包括三个部分:try 块,catch 块和 finally 块。
-
try 块,以关键字 try 开始,后面紧跟一个由表达式与声明组成的块(用一对花括号括起来,定义了新的局部作用域,可以包含任意表达式和声明,后简称“块”),try 后面的块内可以抛出异常,并被紧随的 catch 块所捕获并处理(如果不存在 catch 块或未被捕获,则在执行完 finally 块后,该异常继续被抛出)。
-
catch 块,一个普通 try 表达式可以包含零个或多个 catch 块(当没有 catch 块时必须有 finally 块)。每个 catch 块以关键字 catch 开头,后跟一条 catchPattern 和一个块,catchPattern 通过模式匹配的方式匹配待捕获的异常。一旦匹配成功,则交由其后跟随的块进行处理,并且忽略它后面的其他 catch 块。当某个 catch 块可捕获的异常类型均可被定义在它前面的某个 catch 块所捕获时,会在此 catch 块处报“catch 块不可达”的 warning。
-
finally 块,以关键字 finally 开始,后面紧跟一个块。原则上,finally 块中主要实现一些“善后”的工作,如释放资源等,且要尽量避免在 finally 块中再抛异常。并且无论异常是否发生(即无论 try 块中是否抛出异常),finally 块内的内容都会被执行(若异常未被处理,执行完 finally 块后,继续向外抛出异常)。一个 try 表达式在包含 catch 块时可以不包含 finally 块,否则必须包含 finally 块。
try 后面紧跟的块以及每个 catch 块的的作用域互相独立。
下面是一个只有 try 块和 catch 块的简单示例:
main() {
try {
throw NegativeArraySizeException("I am an Exception!")
} catch (e: NegativeArraySizeException) {
println(e)
println("NegativeArraySizeException is caught!")
}
println("This will also be printed!")
}
执行结果为
NegativeArraySizeException: I am an Exception!
NegativeArraySizeException is caught!
This will also be printed!
catchPattern 中引入的变量作用域级别与 catch 后面的块中变量作用域级别相同,在 catch 块中再次引入相同名字会触发重定义错误。例如:
main() {
try {
throw NegativeArraySizeException("I am an Exception!")
} catch (e: NegativeArraySizeException) {
println(e)
let e = 0 // Error, redefinition
println(e)
println("NegativeArraySizeException is caught!")
}
println("This will also be printed!")
}
下面是带有 finally 块的 try 表达式的简单示例:
main() {
try {
throw NegativeArraySizeException("NegativeArraySizeException")
} catch (e: NegativeArraySizeException) {
println("Exception info: ${e}.")
} finally {
println("The finally block is executed.")
}
}
执行结果为
Exception info: NegativeArraySizeException: NegativeArraySizeException.
The finally block is executed.
try 表达式可以出现在任何允许使用表达式的地方。try 表达式的类型的确定方式,与 if、match 表达式等多分支语法结构的类型的确定方式相似,为 finally 分支除外的所有分支的类型的最小公共父类型。例如下面代码中的 try 表达式和变量 x 的类型均为 E 和 D 的最小公共父类型 D;finally 分支中的 C() 并不参与公共父类型的计算(若参与,则最小公共父类型会变为 C)。
另外,当 try 表达式的值没有被使用时,其类型为 Unit,不要求各分支的类型有最小公共父类型。
open class C { }
open class D <: C { }
class E <: D { }
main () {
let x = try {
E()
} catch (e: Exception) {
D()
} finally {
C()
}
0
}
Try-with-resources 表达式
Try-with-resources 表达式主要是为了自动释放非内存资源。不同于普通 try 表达式,try-with-resources 表达式中的 catch 块和 finally 块均是可选的,并且 try 关键字其后的块之间可以插入一个或者多个 ResourceSpecification 用来申请一系列的资源(ResourceSpecification 并不会影响整个 try 表达式的类型)。这里所讲的资源对应到语言层面即指对象,因此 ResourceSpecification 其实就是实例化一系列的对象(多个实例化之间使用“,”分隔)。使用 try-with-resources 表达式的例子如下所示:
class R <: Resource {
public func isClosed(): Bool {
true
}
public func close(): Unit {
print("R is closed")
}
}
main() {
try (r = R()) {
println("Get the resource")
}
}
程序输出结果为:
Get the resource
try 关键字和 {} 之间引入的名字,其作用域与 {} 中引入的变量作用域级别相同,在 {} 中再次引入相同名字会触发重定义错误。
class R <: Resource {
public func isClosed(): Bool {
true
}
public func close(): Unit {
print("R is closed")
}
}
main() {
try (r = R()) {
println("Get the resource")
let r = 0 // Error, redefinition
println(r)
}
}
Try-with-resources 表达式中的 ResourceSpecification 的类型必须实现 Resource 接口,并且尽量保证其中的 isClosed 函数不要再抛异常:
interface Resource {
func isClosed(): Bool
func close(): Unit
}
需要说明的是,try-with-resources 表达式中一般没有必要再包含 catch 块和 finally 块,也不建议用户再手动释放资源。因为 try 块执行的过程中无论是否发生异常,所有申请的资源都会被自动释放,并且执行过程中产生的异常均会被向外抛出。但是,如果需要显式地捕获 try 块或资源申请和释放过程中可能抛出的异常并处理,仍可在 try-with-resources 表达式中包含 catch 块和 finally 块:
class R <: Resource {
public func isClosed(): Bool {
true
}
public func close(): Unit {
print("R is closed")
}
}
main() {
try (r = R()) {
println("Get the resource")
} catch (e: Exception) {
println("Exception happened when executing the try-with-resources expression")
} finally {
println("End of the try-with-resources expression")
}
}
程序输出结果如下:
Get the resource
End of the try-with-resources expression
Try-with-resources 表达式的类型是 Unit。
CatchPattern 进阶介绍
大多数时候,我们只想捕获某一类型和其子类型的异常,这时候我们使用 CatchPattern 的类型模式来处理;但有时也需要所有异常做统一处理(如此处不该出现异常,出现了就统一报错),这时可以使用 CatchPattern 的通配符模式来处理。
类型模式在语法上有两种格式:
- Identifier: ExceptionClass。此格式可以捕获类型为 ExceptionClass 及其子类的异常,并将捕获到的异常实例转换成 ExceptionClass,然后与 Identifier 定义的变量进行绑定,接着就可以在 catch 块中通过 Identifier 定义的变量访问捕获到的异常实例。
- Identifier: ExceptionClass_1 | ExceptionClass_2 | ... | ExceptionClass_n。此格式可以通过连接符 | 将多个异常类进行拼接,连接符 | 表示“或”的关系:可以捕获类型为 ExceptionClass_1 及其子类的异常,或者捕获类型为 ExceptionClass_2 及其子类的异常,依次类推,或捕获类型为 ExceptionClass_n 及其子类的异常(假设 n 大于 1)。当待捕获异常的类型属于上述“或”关系中的任一类型或其子类型时,此异常将被捕获。但是由于无法静态地确定被捕获异常的类型,所以被捕获异常的类型会被转换成由 | 连接的所有类型的最小公共父类,并将异常实例与 Identifier 定义的变量进行绑定。因此在此类模式下,catch 块内只能通过 Identifier 定义的变量访问 ExceptionClass_i(1 <= i <= n) 的最小公共父类中的成员变量和成员函数。当然,也可以使用通配符代替类型模式中的 Identifier,差别仅在于通配符不会进行绑定操作。
示例如下:
main(): Int64 {
try {
throw IllegalArgumentException("This is an Exception!")
} catch (e: OverflowException) {
println(e.message)
println("OverflowException is caught!")
} catch (e: IllegalArgumentException | NegativeArraySizeException) {
println(e.message)
println("IllegalArgumentException or NegativeArraySizeException is caught!")
} finally {
println("finally is executed!")
}
return 0
}
执行结果:
This is an Exception!
IllegalArgumentException or NegativeArraySizeException is caught!
finally is executed!
关于“被捕获异常的类型是由 | 连接的所有类型的最小公共父类”的示例:
open class Father <: Exception {
var father: Int32 = 0
}
class ChildOne <: Father {
var childOne: Int32 = 1
}
class ChildTwo <: Father {
var childTwo: Int32 = 2
}
main() {
try {
throw ChildOne()
} catch (e: ChildTwo | ChildOne) {
println("ChildTwo or ChildOne?")
}
}
执行结果:
ChildTwo or ChildOne?
通配符模式的语法是 _,它可以捕获同级 try 块内抛出的任意类型的异常,等价于类型模式中的 e: Exception,即捕获 Exception 子类所定义的异常。示例:
// Catch with wildcardPattern.
try {
throw OverflowException()
} catch (_) {
println("catch an exception!")
}
最后
有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)资料用来跟着学习是非常有必要的。
这份鸿蒙(HarmonyOS NEXT)资料包含了鸿蒙开发必掌握的核心知识要点,
内容包含了:(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、OpenHarmony南向开发、鸿蒙项目实战等等)鸿蒙(HarmonyOS NEXT)技术知识点。
希望这一份鸿蒙学习资料能够给大家带来帮助,有需要的小伙伴自行领取,限时开源,先到先得~无套路领取!!
如果你是一名有经验的资深Android移动开发、Java开发、前端开发、对鸿蒙感兴趣以及转行人员,可以直接领取这份资料
获取这份完整版高清学习路线,请点击关注 → 公众皓 :【蜀道衫】即可领取
鸿蒙(HarmonyOS NEXT)最新学习路线
-
HarmonOS基础技能
- HarmonOS就业必备技能
- HarmonOS多媒体技术
- 鸿蒙NaPi组件进阶
- HarmonOS高级技能
- 初识HarmonOS内核
- 实战就业级设备开发
有了路线图,怎么能没有学习资料呢,小编也准备了一份联合鸿蒙官方发布笔记整理收纳的一套系统性的鸿蒙(OpenHarmony )学习手册(共计1236页)与鸿蒙(OpenHarmony )开发入门教学视频,内容包含:ArkTS、ArkUI、Web开发、应用模型、资源分类…等知识点。
获取以上完整版高清学习路线, 请点击关注 → 公众皓 :【蜀道衫】即可领取
《鸿蒙 (OpenHarmony)开发入门教学视频》
《鸿蒙生态应用开发V2.0白皮书》
《鸿蒙 (OpenHarmony)开发基础到实战手册》
OpenHarmony北向、南向开发环境搭建
《鸿蒙开发基础》
- ArkTS语言
- 安装DevEco Studio
- 运用你的第一个ArkTS应用
- ArkUI声明式UI开发
- .……
《鸿蒙开发进阶》
- Stage模型入门
- 网络管理
- 数据管理
- 电话服务
- 分布式应用开发
- 通知与窗口管理
- 多媒体技术
- 安全技能
- 任务管理
- WebGL
- 国际化开发
- 应用测试
- DFX面向未来设计
- 鸿蒙系统移植和裁剪定制
- ……
《鸿蒙进阶实战》
- ArkTS实践
- UIAbility应用
- 网络案例
- ……
总结
总的来说,华为鸿蒙不再兼容安卓,对中年程序员来说是一个挑战,也是一个机会。只有积极应对变化,不断学习和提升自己,他们才能在这个变革的时代中立于不败之地。
看完三件事❤️
- 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
- 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
- 关注作者公众皓 『 蜀道衫 』,不定期分享原创知识。
- 关注后回复【666】扫码即可获取学习资料包。
- 同时可以期待后续文章ing🚀。