前情提要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這種方式差。不得不說,雖然寫起來比較囉嗦,但看着這個結構確實是一目瞭然。
可是無論如何,在我看來,函數式的錯誤處理依舊是頗有趣的一個東西。若是合適的話,能夠多在代碼中嘗試去使用:)
以上~