前情提要
Scala 函数式编程指南(一) 函数式思想介绍
scala 函数式编程(二) scala 基础语法介绍
Scala 函数式编程(三) scala 集合和函数
Scala 函数式编程 (四) 函数式的数据结构 上
Scala 函数式编程 (四) 函数式的数据结构 下
1. 面向对象的错误处理
在介绍 scala 的函数式的错误处理之前, 我们要先来介绍一下其他情况下的错误处理方式.
以 java 为例, 常见的错误处理方式不外乎两种, 一种是及时捕捉到异常, 然后当场进行处理.
- try{
- ...
- }catch(Exception e){
- ...
- }finally{
- }
另一种则是将异常抛出, 层层捕获, 然后在最上层对异常进行统一处理, 这种通常是在大型项目的时候会使用.
这两种错误处理的方法是, 在我们日常的编程中, 已经足以应对多种情况.
但在函数式编程中却不行, 函数式编程追求的是无副作用的代码, 无副作用最直接的应用就是可以放心得并发运行, 而抛出异常却会产生副作用.
try catch 处理的弊端, 在并发编程中其实有较为明显的体现.
以 spark 为例, 如果 spark 主节点 master 询问 worker 节点的健康情况, 当 worker 节点出现异常时, 显然让 master 节点来捕获并处理这个异常, 有点不符合情理.
更合理的处理, 应该是让 master 接收到一个表示错误情况的消息, 然后再决定接下来如何处理. 而 worker 的异常就让 worker 自己去解决吧.
而在 scala 中, 有一种特定的类型, 它用来表示可能导致异常的一个计算过程, 这就是 Try.
2. 从 Option 到 Try
前面有介绍过 Option, 相关介绍可以看这里 Scala 函数式编程(三) scala 集合和函数.
这里简单介绍一下 Option.
Option 呢, 其实就是薛定谔的值, 里面可能有值, 也可能没有值. 只有到要看的时候, 才会知道 Option 里面到底有没有值.
Option 全程叫 Option[A], 表示 Option 里面存的是 A 类型的值, 这个 A 可以是 Int,String, 等等. 我们可以通过 get 这个 API 来获取 Option[A]里面的值, 当不存在时, get 会返回 None.
可以通过 isEmpty, 来确认 Option 里面到底是不是有值. 也可以通过 getOrElse 来指定没有值的时候要返回什么值.
Try[A]和 Option 类似, 都是表示一个可能有也可能没有的东西. 实际对应过来, Try[A]就表示一个可能成功也可以失败的计算, 如果成功, 则返回 A 类型, 如果失败, 则返回 Throwable.
先最在交互式环境中直观看一下怎么使用吧:
- scala> import scala.util.Try
- import scala.util.Try
- scala> Try(1+1)
- res15: scala.util.Try[Int] = Success(2)
- scala> Try(1/0)
- res16: scala.util.Try[Int] = Failure(java.lang.ArithmeticException: / by zero)
能够实现这个功能, 主要是因为 Try 的两个子类型:
Success[A]: 代表成功的计算.
封装了 Throwable 的 Failure[A]: 代表出了错的计算.
是不是和 Option 很像呢? 也是薛定谔的错误, 在没打开来看之前, Try 里面可能是成功的, 也可能是失败的.
同样可以通过 isSuccess 和 isFailure 来确认到底这个 Try 是成功还是失败.
如果一个函数中有一个计算可能会出错, 那么我们就可以直让函数返回 Try, 然后对成功还是错误, 就全交由调用者来进行处理, 比如上面说到的, Spark 的那个例子.
3.Try 的使用
上面初步介绍了 Try 的含义和用法, 接下来就来看看 Try 这个东西, 还有哪些常规的用法吧.
3.1 map
map 是 scala 里面非常常用的一种操作, Try 里面也有!
对 Try 使用 Map 的话, 会将一个是 Success[A]的 Try[A]映射到 Try[B]会得到 Success[B]. 如果它是 Failure[A], 就会得到 Failure[B], 而且包含的异常和 Failure[A]一样.
看看例子吧:
- // 新建一个 Try, 注意, 这里是 Try[Int]
- scala> val tryMap = Try(1+1)
- tryMap: scala.util.Try[Int] = Success(2)
- // 使用 Map, 让它变成 Try[String]了
- scala> tryMap.map(_.toString)
- res46: scala.util.Try[String] = Success(2)
- // 新建一个会失败的 Try[Int]
- scala> val tryMapFail = Try(1 / 0)
- tryMapFail: scala.util.Try[Int] = Failure(java.lang.ArithmeticException: / by zero)
- // 转换成 Try[String]了, 但 Failure 的异常类型不变
- scala> tryMapFail.map(_.toString)
- res47: scala.util.Try[String] = Failure(java.lang.ArithmeticException: / by zero)
Try 不止支持 map, 还支持 for,flatMap,filter 等常规操作, 从这个角度看, Try 反而更像一种数据结构.
3.2 错误时候的默认值 getOrElse
和 Option 一样, Try 还很方便得提供了 getOrElse 这个方法. 当你想为失败的时候做些什么的时候就可以用这个 API.
这个我举个简单的例子, 将字符串转换为 Int 类型. 在字符串转 Int 类型的时候呢, 可能会遇到一些不符合规范的数据. 这时候你就不得不考虑数据是否可以安全得转换成 Int, 但有了 Try, 可以很方便得用 getOrElse, 方法.
当遇到不能转成 Int 的字符串, 给与一个默认值即可.
- scala> import scala.util.Try
- import scala.util.Try
- scala> "12".toInt
- res17: Int = 12
- scala> "asd".toInt
- java.lang.NumberFormatException: For input string: "asd"
- at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
- at java.lang.Integer.parseInt(Integer.java:580)
- at java.lang.Integer.parseInt(Integer.java:615)
- at scala.collection.immutable.StringLike$class.toInt(StringLike.scala:272)
- at scala.collection.immutable.StringOps.toInt(StringOps.scala:29)
- ... 32 elided
- scala> Try("asd".toInt).getOrElse(-1)
- res19: Int = -1
但这里还是得多说一句, 这种做法会忽略掉原本应该抛出的错误, 你需要明确知道自己确实是要忽略掉这个错误才能这样用.
否则可能因为设置的默认值导致出现问题, 而毫无头绪, 因为程序并没有报任何错误!!
3.3 模式匹配
我们可以不必如 java 的 try catch 那般去处理 Try 失败时返回的异常. 因为我们有 scala 的模式匹配.
不得不说, 模式匹配真的是很强大的一个语言特性. 前面不是说到嘛, Try 有两个子类, Success 和 Failure, 成功时候返回 Success, 失败时返回 Failure.
所以我们就能够这样做:
- import scala.util.Success
- import scala.util.Failure
- val operation = Try(1 / 0)
- operation match {
- case Success(num) => println(num)
- case Failure(ex) => println(s"Problem is ${ex.getMessage}")
- }
因为除数为 0, 所以这个 Try 是失败的, 所以这里会输出: Problem is / by zero
scala 强大的模式匹配, 可以方便得让我们处理错误和非错误的情况.
4. 小结
Scala 的错误处理和其他范式的编程语言有很大的不同. Try 类型可以让你将可能会出错的计算封装在一个容器里, 并优雅的去处理计算得到的值. 并且可以像操作集合和 Option 那样统一的去操作 Try.
同时 Try[A]也支持常见数据结构中的操作, 诸如 Map,Filter 等常规的 API 都支持.
Try 这种错误处理的方式, 明显更适用于函数式的情况, 也就是说更适合在并发编程的时候使用.
但在我看来, Try 也是有一些不好的地方, 比如说在代码可读性方面就比 try catch 这种方式差. 不得不说, 虽然写起来比较啰嗦, 但看着这个结构确实是一目了然.
但是不管如何, 在我看来, 函数式的错误处理依旧是很有趣的一个东西. 如果合适的话, 可以多在代码中尝试去使用:)
以上~
来源: https://www.cnblogs.com/listenfwind/p/12337437.html