译者注:原文出处http://danielwestheide.com/blog/2012/12/26/the-neophytes-guide-to-scala-part-6-error-handling-with-try.html,翻译:Thomas
在新语言的学习阶段,你通常不会去想如果执行代码出了问题该如何处理。一旦你想要开发一个真实产品时,就必须认真面对错误和异常处理了。由于各种个语言对这方面支持程度的重要性有时候被认人忽视了。
Scala在设计之初就考虑了如何优雅的应对错误场景。在本篇中,我会介绍Scala以Try类型为基础的错误处理机制及内在原理。后面会用到Scala2.10才有的一些特性,2.9.3里也移植了这些特性,所以请确保你的实验环境是在2.9.3或以上版本。
抛出和捕捉异常
在介绍Scala错误处理的惯用方式之前,我们先来看看你在其它语言如Java或Ruby中的错误的方式。和这些语言一样,Scala里你也可以抛出一个异常:
case class Customer(age: Int) class Cigarettes case class UnderAgeException(message: String) extends Exception(message) def buyCigarettes(customer: Customer): Cigarettes = if (customer.age < 16) throw UnderAgeException(s"Customer must be older than 16 but was ${customer.age}") else new Cigarettes
抛出的异常可以用类似Java的方式来捕捉和处理,不一样的是,你定义一个偏函数来进行异常处理。另外,在Scala里try/catch是一个表达式,所以下面的代码返回异常里的信息:
val youngCustomer = Customer(15) try { buyCigarettes(youngCustomer) "Yo, here are your cancer sticks! Happy smokin'!" } catch { case UnderAgeException(msg) => msg }
函数式的错误处理
在你的代码中处处出现这种“传统”的错误处理代码会让人觉得很难看,并且这也不算是函数式编程。并且当程序有很多并发代码时,这也不是个好的方法,比如说你就没法捕捉到执行在另一个线程的一个Actor抛出的异常 - 你可能希望收到一个表示异常/错误情况的消息。
在Scala里,首选的做法是从函数返回一个适当的值来表示发生了一个错误,不必担心,我们并没有回到C语言风格的错误处理,在C语言里,函数和调用方通过约定的错误代码来传递异常。在Scala里我们用一个特别的类型来表示一个可能会发生异常的计算。
在本篇,我们专注于从Scala2.10(2.9.3里也引进了)引进来的类型,Try。有一个叫Either的类似类型也挺有用,会在下一篇介绍到。
Try的含义
解释Try的含义的最好方法是拿Option类型的含义来参考。Option[A]表示一个A类型的值可能存在或不存在,那么Try[A]表示的是一段代码当成功执行时返回A类型的值,或者当错误发生时返回一个Throwable。这种包含可能存在错误的类型的实例可以很容易的在你程序中各个并发代码中互相传递。
Try有两个形态:如果一个Try[A]实例表示计算成功,它会是一个Success[A]的实例,包含着A类型的值,另一个形态是当代码出错时,它是一个Failure[A]的实例,包含一个Throwable,表示一个异常或其它类型的错误。
如果我们确切的知道一段代码可能会出错,我们可以让这个函数返回Try[A]。这显式化的表达了可能的错误并且强制函数的调用者处理这种可能的错误。
举个例子,我们要写一个简单地网页抓取器,用户可以提供要抓取的URL地址。我们应用少不了这样的功能:解析用户输入的URL并据此生成java.net.URL实例:
import scala.util.Try import java.net.URL def parseURL(url: String): Try[URL] = Try(new URL(url))
如你所见,我们返回的是Try[URL]类型。如果提供的url在语法上是个URL,那么返回的是
Success[URL],如果URL构造器抛出MalformedURLException,返回类型将是Failure[URL]。
我们用了Try的伙伴对象的apply工厂方法,这方法输入一个by-name方式的URL类型的参数。在我们的例子中,new URL(url)
只在Try的apply方法里执行,这方法里执行的代码抛出的任何non-fatal异常都会被捕捉并封装在Failure 类型中返回。
因此,parseURL("http://danielwestheide.com")
会返回包含相应URL实例的Success[URL]
,而parseURL("garbage")会返回包含
MalformedURLException的Failure[URL]。
使用Try值
使用Try实例和使用Option类型非常相似。因此这里就没有太多惊喜了。
你可以呼叫isSuccess方法来判断Try是否成功,然后如果成功就调用它的get方法(这和上篇的Option的isDefined相似)。但是相信我,没有太多情况让你想要这样做。
也可以用getOrElse来传递一个默认值,如果Try是一个Failure时将返回这默认值:
val url = parseURL(Console.readLine("URL: ")) getOrElse new URL("http://duckduckgo.com")
如果用户提供的URL格式有错误,我们就用后备的URL,DuckDuckGo。
串联操作
Try类型的一个最重要的特性就是,和Option一样,它也支持你在所有集合类型里看到的高阶方法。在下面的例子中你会看到,这让你以一种可读性很强的方式串联很多Try并且捕获其中任何的异常。
Map和flat map
把一个为Success[A]
的Try[A] map到
Try[B]的结果
是Success[B]
实例。
如果实例为Failure[A]
, 那么Try[B]的map结果就是
Failure[B]
, 也就是把原来Failure[A]里的异常传递下来
:
parseURL("http://danielwestheide.com").map(_.getProtocol) // results in Success("http") parseURL("garbage").map(_.getProtocol) // results in Failure(java.net.MalformedURLException: no protocol: garbage)
如果你串联多个map操作,结果就会变成嵌套的
Try
结构,者往往不是你想要的。考虑下面的方法,该方法从给定的URL中获取内容的InputStream:
import java.io.InputStream def inputStreamForURL(url: String): Try[Try[Try[InputStream]]] = parseURL(url).map { u => Try(u.openConnection()).map(conn => Try(conn.getInputStream)) }因为传递给两次
map方法的匿名函数返回的是
Try
, 所以最终返回类型就是Try[Try[Try[InputStream]]]。这时候就该让
flatMap派上用场了,Try[A]的flatMap的参数为一个函数,这函数以A类型作为参数,输出Try[B]类型。如果Try[A]为一个Failure[A],那么将返回Failure[B],如果Try[A]是一个Success[A],flatMap则会解包出A并且返回一个Try[B]。
这意味着我们通过串联任意多个flatMap构造一个管道来进行一系列的计算,这些计算需要封装在Success中得值,管道中的任何一个操作丢出封装在Failure中的异常,就意味着最终结果会是Failure。我们用flatMap来重写一下inputStreamForURL方法:
def inputStreamForURL(url: String): Try[InputStream] = parseURL(url).flatMap { u => Try(u.openConnection()).flatMap(conn => Try(conn.getInputStream)) }现在我们会得到
Try[InputStream]的结果,如果任意一个flatMap中得方法失败,结果是封装在Failure中的异常
, 反之是封装在Success中得InputStream。
过滤和foreach
你还可以过滤一个Try或者呼叫它的foreach方法。这两个的用法和你在前面Option中学到的基本一样。
当Try是一个Failure或过滤方法返回false时,filter方法返回一个Failure。如果呼叫一个Success的filter方法并且过滤方法返回true,那么原始的Success会被返回:
def parseHttpURL(url: String) = parseURL(url).filter(_.getProtocol == "http") parseHttpURL("http://apache.openmirror.de") // results in a Success[URL] parseHttpURL("ftp://mirror.netcologne.de/apache.org") // results in a Failure[URL]仅当Try是Success时,传递给foreach方法的函数才被调用,foreach方法让你执行一个副作用。Success所包含的值被传递给foreach的函数参数,该函数被执行一次:
parseHttpURL("http://danielwestheide.com").foreach(println)
For语句
因为Try有提供flatMap,map以及filter的方法,那就意味着你可以用For语句的写法来串联Try实例的操作,For语句通常提供更好的可读性。我们用for语法来实现一个给定URL的网页内容的方法:
import scala.io.Source def getURLContent(url: String): Try[Iterator[String]] = for { url <- parseURL(url) connection <- Try(url.openConnection()) is <- Try(connection.getInputStream) source = Source.fromInputStream(is) } yield source.getLines()
有三个地方可能会发生异常,所有这些地方都用Try来加以保护。首先是先前实现的parseURL方法,它返回Try[URL],只有当它返回Success[URL],我们才去尝试打开一个连接并构造一个新的inputStream。如果打开连接和构造inputStream都成功了,我们继续进行,最终产生网页的所有行。用这样高效的方式调用flatMap,最终结果会是Try[Iterator[String]]。
请注意上面的代码有两处可改善的,首先整个功能可以简化成只调用Source.fromURL即可,另外我们在程序结束时没有关闭所构造的inputStream,我还是这样写代码是想把焦点放在我们应该关注的方面,即Try。
模式匹配
在你的代码里,你可能经常需要知道从一个函数返回的Try实例代表着成功还是失败,并据此执行不同的代码分支,这时候你需要模式匹配,因为Success和Failure都是case class,所以这很容易实现。
我们想要把网页的内容显示出来,或者如果中间出现异常就打印一个错误信息:
import scala.util.Success import scala.util.Failure getURLContent("http://danielwestheide.com/foobar") match { case Success(lines) => lines.foreach(println) case Failure(ex) => println(s"Problem rendering URL content: ${ex.getMessage}") }如果你想要为Failure的场景设定一个默认行为,无需使用getOrElse,替代的方式是用recover方法,该方法需要传入一个偏函数,并返回另一个Try,如果recover被Success实例呼叫,拿它直接返回该实例,否则,返回包在Success里的偏函数的返回值。
import java.net.MalformedURLException import java.io.FileNotFoundException val content = getURLContent("garbage") recover { case e: FileNotFoundException => Iterator("Requested page does not exist") case e: MalformedURLException => Iterator("Please make sure to enter a valid URL") case _ => Iterator("An unexpected error has occurred. We are so sorry!") }现在我们可以放心的对content调用它的get方法以获取 Try[Iterator[String]]里包着的结果了。调用content.get.foreach(println)将会输出“Please make sure to enter a valid URL”。
结论
Scala里的惯用的错误处理机制和其它的语言如Java或Ruby差别很大。Try类型让你可以封装代码计算结果的出错并且可以以一种优雅的方式将计算结果作串联操作。你可以像用集合或Option一样的方式来进行错误处理。
限于篇幅,我没有介绍Try的所有方法。像Option一样,Try也有orElse方法。transform和recoveryWith方法也值得一看,我建议你去查看Scala的文档并试着写写代码。
在下一篇,我们会接触到Either,她是封装代码异常的另一种方案,不过除了进行错误处理,它还用在其它场景。
相关推荐
1. **Error Handling**:ZIO使用了类型系统来处理错误,所有的错误都被编码为Effect的一部分,避免了传统try-catch语句的混乱。 2. **Circuit Breaker**:ZIO内置了熔断器机制,能够防止服务因过多的失败请求而过载...
在SparkCodingChallenge中,我们可以预见...同时,熟悉Scala语言的特性和最佳实践也非常重要,例如使用Option和Try来处理可能的null值,利用for-comprehensions进行控制流,以及使用pattern matching来解构复杂类型等。
一、项目简介 包含:项目源码、数据库脚本等,该项目附带全部源码可作为毕设使用。 项目都经过严格调试,eclipse或者idea 确保可以运行! 该系统功能完善、界面美观、操作简单、功能齐全、管理便捷 二、技术实现 jdk版本:1.8 及以上 ide工具:IDEA或者eclipse 数据库: mysql5.5及以上 后端:spring+springboot+mybatis+maven+mysql 前端: vue , css,js , elementui 三、系统功能 1、系统角色主要包括:管理员、用户 2、系统功能 前台功能包括: 用户登录 车位展示 系统推荐车位 立即预约 公告展示 个人中心 车位预定 违规 余额充值 后台功能: 首页,个人中心,修改密码,个人信息 用户管理 管理员管理 车辆管理 车位管理 车位预定管理,统计报表 公告管理 违规管理 公告类型管理 车位类型管理 车辆类型管理 违规类型管理 轮播图管理 详见 https://flypeppa.blog.csdn.net/article/details/146122666
项目已获导师指导并通过的高分毕业设计项目,可作为课程设计和期末大作业,下载即用无需修改,项目完整确保可以运行。 包含:项目源码、数据库脚本、软件工具等,该项目可以作为毕设、课程设计使用,前后端代码都在里面。 该系统功能完善、界面美观、操作简单、功能齐全、管理便捷,具有很高的实际应用价值。 项目都经过严格调试,确保可以运行!可以放心下载 技术组成 语言:java 开发环境:idea 数据库:MySql 部署环境:maven 数据库工具:navica 更多毕业设计https://cv2022.blog.csdn.net/article/details/124463185
内容为Python程序设计的思维导图,适用于新手小白进行浏览,理清思路
2024-Stable Diffusion全套资料(软件+关键词+模型).rar
mmexport1741417035005.png
COMSOL三维锂离子电池全耦合电化学热应力模型:模拟充放电过程中的多物理场耦合效应及电芯内应力应变情况,COMSOL锂离子电池热应力全耦合模型,comsol三维锂离子电池电化学热应力全耦合模型锂离子电池耦合COMSOL固体力学模块和固体传热模块,模型仿真模拟电池在充放电过程中由于锂插层,热膨胀以及外部约束所导致的电极的应力应变情况结果有电芯中集流体,电极,隔膜的应力应变以及压力情况等,电化学-力单向耦合和双向耦合 ,关键词: 1. COMSOL三维锂离子电池模型; 2. 电化学热应力全耦合模型; 3. 锂离子电池; 4. 固体力学模块; 5. 固体传热模块; 6. 应力应变情况; 7. 电芯中集流体; 8. 电极; 9. 隔膜; 10. 电化学-力单向/双向耦合。,COMSOL锂离子电池全耦合热应力仿真模型
基于传递矩阵法的一维层状声子晶体振动传输特性及其优化设计与应用,声子晶体传递矩阵法解析及应用,Matlab 一维层状声子晶体振动传输特性 传递矩阵法在声子晶体的设计和应用中具有重要作用。 通过调整声子晶体的材料、周期和晶格常数等参数,可以设计出具有特定带隙结构的声子晶体,用于滤波、减震、降噪等应用。 例如,通过调整声子晶体的周期数和晶格常数,可以改变带隙的位置和宽度,从而实现特定的频率范围内的噪声控制。 此外,传递矩阵法还可以用于分析和优化声子晶体的透射谱,为声学器件的设计提供理论依据。 ,Matlab; 一维层状声子晶体; 振动传输特性; 传递矩阵法; 材料调整; 周期和晶格常数; 带隙结构; 滤波; 减震; 降噪; 透射谱分析; 声学器件设计,Matlab模拟声子晶体振动传输特性及优化设计研究
头部姿态估计(HeadPose Estimation)-Android源码
永磁同步电机FOC、MPC与高频注入Simulink模型及基于MBD的代码生成工具,适用于Ti f28335与dspace/ccs平台开发,含电机控制开发文档,永磁同步电机控制技术:FOC、MPC与高频注入Simulink模型开发及应用指南,提供永磁同步电机FOC,MPC,高频注入simulink模型。 提供基于模型开发(MBD)代码生成模型,可结合Ti f28335进行电机模型快速开发,可适用dspace平台或者ccs平台。 提供电机控制开发编码器,转子位置定向,pid调试相关文档。 ,永磁同步电机; FOC控制; MPC控制; 高频注入; Simulink模型; 模型开发(MBD); Ti f28335; 电机模型开发; dspace平台; ccs平台; 编码器; 转子位置定向; pid调试。,永磁同步电机MPC-FOC控制与代码生成模型
light of warehouse.zip
内容概要:文章深入讨论了工业乙醇发酵的基本原理及工艺流程,特别是在温度和气体排放(如CO2及其他有害气体)影响下的发酵效果分析。文章介绍了乙醇发酵的重要环节,如糖分解、代谢路径、代谢调控以及各阶段的操作流程,重点展示了如何通过Matlab建模和仿真实验来探索这两个关键环境因素对发酵过程的具体影响。通过动态模型仿真分析,得出合适的温度范围以及适时排除CO2能显著提升发酵产乙醇的效果与效率,从而提出了基于仿真的优化发酵生产工艺的新方法。 适用人群:从事生物工程相关领域研究的科学家、工程师及相关专业师生。 使用场景及目标:适用于实验室环境、学术交流会议及实际生产指导中,以提升研究人员对该领域内复杂现象的理解能力和技术水平为目标。 其他说明:附录中有详细的数学公式表达和程序代码可供下载执行,便于有兴趣的研究团队重复实验或者继续扩展研究工作。
本资源包专为解决 Tomcat 启动时提示「CATALINA_HOME 环境变量未正确配置」问题而整理,包含以下内容: 1. **Apache Tomcat 9.0.69 官方安装包**:已验证兼容性,解压即用。 2. **环境变量配置指南**: - Windows 系统下 `CATALINA_HOME` 和 `JAVA_HOME` 的详细配置步骤。 - 常见错误排查方法(如路径含空格、未生效问题)。 3. **辅助工具脚本**:一键检测环境变量是否生效的批处理文件。 4. **解决方案文档**:图文并茂的 PDF 文档,涵盖从报错分析到成功启动的全流程。 适用场景: - Tomcat 9.x 版本环境配置 - Java Web 开发环境搭建 - 运维部署调试 注意事项: - 资源包路径需为纯英文,避免特殊字符。 - 建议使用 JDK 8 或更高版本。
这是一款仿照京东商城的Java Web项目源码,完美复现了360buy的用户界面和购物流程,非常适合Java初学者和开发者进行学习与实践。通过这份源码,你将深入了解电商平台的架构设计和实现方法。欢迎大家下载体验,提升自己的编程能力!
系统选用B/S模式,后端应用springboot框架,前端应用vue框架, MySQL为后台数据库。 本系统基于java设计的各项功能,数据库服务器端采用了Mysql作为后台数据库,使Web与数据库紧密联系起来。 在设计过程中,充分保证了系统代码的良好可读性、实用性、易扩展性、通用性、便于后期维护、操作方便以及页面简洁等特点。
这是一款专为大学生打造的求职就业网JavaWeb毕业设计源码,功能齐全,界面友好。它提供简历投递、职位搜索、在线交流等多种实用功能,能够帮助你顺利进入职场。无论你是想提升技术水平还是寻找灵感,这个源码都是不可多得的资源。快来下载,让你的求职之路更加顺畅吧!
useTable(1).ts
实验一: 1、进行CCS6.1软件的安装,仿真器的设置,程序的编译和调试; 2、熟悉CCS软件中的C语言编程; 3、使用按键控制LED跑马灯的开始与停止、闪烁频率; 4、调试Convolution、FFT、FIR、FFT-FIR实验,编制IIR算法并调试,并在CCS软件上给出实验结果。 实验二: 1、利用定时器周期中断或下溢中断和比较器比较值的修改来实现占空比可调的PWM波形; 2、改变PWM占空比控制LED灯的亮暗,按键实现10级LED灯亮暗调整; 3、模拟数字转换,转换过程中LED指示,并在变量窗口显示转换结果; 4、数字模拟转换,产生一个正弦波,转换过程中LED指示,转换完成后在CCS调试窗口显示波形。 实验三: 1、SCI异步串行通信实验; 2、SPI及IIC同步串行通信实验; 3、CAN现场总线串行通信实验; 4、传输过程中LED指示。 实验四: 1、电机转速控制实验。