前情提要html
在介绍scala的函数式的错误处理以前,咱们要先来介绍一下其余状况下的错误处理方式。并发
以java为例,常见的错误处理方式不外乎两种,一种是及时捕捉到异常,而后当场进行处理。编程语言
try{ ... }catch(Exception e){ ... }finally{ }
另外一种则是将异常抛出,层层捕获,而后在最上层对异常进行统一处理,这种一般是在大型项目的时候会使用。ide
这两种错误处理的方法是,在咱们平常的编程中,已经足以应对多种状况。函数式编程
但在函数式编程中却不行,函数式编程追求的是无反作用的代码,无反作用最直接的应用就是能够放心得并发运行,而抛出异常却会产生反作用。
try catch处理的弊端,在并发编程中其实有较为明显的体现。
以spark为例,若是spark主节点master询问worker节点的健康状况,当worker节点出现异常时,显然让master节点来捕获并处理这个异常,有点不符合情理。
更合理的处理,应该是让master接收到一个表示错误状况的消息,而后再决定接下来如何处理。而worker的异常就让worker本身去解决吧。
而在scala中,有一种特定的类型,它用来表示可能致使异常的一个计算过程,这就是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的两个子类型:
是否是和Option很像呢?也是薛定谔的错误,在没打开来看以前,Try里面多是成功的,也多是失败的。
一样能够经过isSuccess和isFailure来确认到底这个Try是成功仍是失败。
若是一个函数中有一个计算可能会出错,那么咱们就能够直让函数返回Try,而后对成功仍是错误,就全交由调用者来进行处理,好比上面说到的,Spark的那个例子。
上面初步介绍了Try的含义和用法,接下来就来看看Try这个东西,还有哪些常规的用法吧。
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反而更像一种数据结构。
和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
但这里仍是得多说一句,这种作法会忽略掉本来应该抛出的错误,你须要明确知道本身确实是要忽略掉这个错误才能这样用。
不然可能由于设置的默认值致使出现问题,而毫无头绪,由于程序并无报任何错误!!
咱们能够没必要如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强大的模式匹配,能够方便得让咱们处理错误和非错误的状况。
Scala 的错误处理和其余范式的编程语言有很大的不一样。 Try 类型可让你将可能会出错的计算封装在一个容器里,并优雅的去处理计算获得的值。 而且能够像操做集合和 Option 那样统一的去操做 Try。
同时Try[A]也支持常见数据结构中的操做,诸如Map,Filter等常规的api都支持。
Try这种错误处理的方式,明显更适用于函数式的状况,也就是说更适合在并发编程的时候使用。
但在我看来,Try也是有一些很差的地方,好比说在代码可读性方面就比try catch这种方式差。不得不说,虽然写起来比较啰嗦,但看着这个结构确实是一目了然。
可是无论如何,在我看来,函数式的错误处理依旧是颇有趣的一个东西。若是合适的话,能够多在代码中尝试去使用:)
以上~