避免 null 使用

大多数语言都有个特殊的关键字或对象来表示个对象引用的是"无" 在 Java 它是 null。在 Java 里 null 是个关键字 不是个对象 所以对它调用任何方法都是非法的。但是这对语言设计者来说是一件令人疑惑的选择。为什么要在程序员希望返回个对象的时候返回个关键字呢?

Scala 的 Option类型

为了让所有东西都是对象的目标更加一致 也为了遵循函数式编程的习惯 Scala 鼓励你在变量和函数返回值可能不会引用任何值的时候使用 Option 类型。在没有值的时候 使用 None 这是 Option 的个子类。如果有值可以引用 就使用 Some 来包含这个值。Some 也是 Option 的子类。None 被声明为个对象 而不是个类 因为 只需要它的个实例。这样 它多少有点像 null 关键字 但它却是个实实在在的 有方法的对象。

应用例子

Option 类型的值通常作为 Scala 集合类型(List, Map 等)操作的返回类型。如 Map 的 get 方法:

scala> val capitals = Map("France"->"Paris", "Japan"->"Tokyo", "China"->"Beijing")capitals: scala.collection.immutable.Map[String,String] = Map(France -> Paris, Japan -> Tokyo, China -> Beijing)scala> capitals get "France"res0: Option[String] = Some(Paris)scala> capitals get "North Pole"res1: Option[String] = None

Option 有两个子类别 Some 和 None。当程序回传 Some 的时候 代表这个函式成功地给了你个 String 而你可以透过 get() 函数拿到那个 String 如果程序返回的是 None 则代表没有字符串可以给你。

在返回 None 也就是没有 String 给你的时候 如果你还硬要调用 get() 来取得 String 的话 Scala 一样是会抛出个 NoSuchElementException 异常给你的。 也可以选用另外个方法 getOrElse。这个方法在这个 Option 是 Some 的实例时返回对应的值 而在是 None 的实例时返回传入的参数。换句话说 传入 getOrElse 的参数实际上是默认返回值。
scala> capitals get "North Pole" getwarning: there was one feature warning; re-run with -feature for detailsjava.util.NoSuchElementException: None.get at scala.None$.get(Option.scala:347) at scala.None$.get(Option.scala:345) ... 33 elidedscala> capitals get "France" getwarning: there was one feature warning; re-run with -feature for detailsres3: String = Parisscala> (capitals get "North Pole") getOrElse "Oops"res7: String = Oopsscala> capitals get "France" getOrElse "Oops"res8: String = Paris

通过模式匹配分离可选值 如果匹配的值是 Some 的话 将 Some 里的值抽出赋给 x 变量:

def showCapital(x: Option[String]) = x match {  case Some(s) => s  case None => "?"}

提示

Scala 程序使用 Option 非常频繁 在 Java 中使用 null 来表示空值 代码中很多地方都要添加 null 关键字检测 不然很容易出现 NullPointException。因此 Java 程序需要关心那些变量可能是 null,而这些变量出现 null 的可能性很低 但一但出现 很难查出为什么出现 NullPointerException。Scala 的 Option 类型可以避免这种情况 因此 Scala 应用推荐使用 Option 类型来代表一些可选值。使用 Option 类型 读者一眼就可以看出这种类型的值可能为 None。

实际上 多亏 Scala 的静态类型 你并不能错误地尝试在个可能为 null 的值上调用方法。虽然在 Java 中这是个很容易犯的错误 它在 Scala 却通不过编译 这是因为 Java 中没有检查变量是否为 null 的编程作为变成 Scala 中的类型错误(不能将 Option[String] 当做 String 来使用)。所以 Option 的使用极强地鼓励了更加弹性的编程习惯。

详解 Option[T]

在 Scala 里 Option[T] 实际上是个容器 就像数组或是 List 一样 你可以把 看成是个可能有零到个元素的 List。

当你的 Option 里面有东西的时候 这个 List 的长度是 1(也就是 Some) 而当你的 Option 里没有东西的时候 它的长度是 0(也就是 None)。

for 循环

如果 把 Option 当成一般的 List 来用 并且用个 for 循环来走访这个 Option 的时候 如果 Option 是 None 那这个 for 循环里的程序代码自然不会执行 于是 就达到了「不用检查 Option 是否为 None 这件事。

scala> val map1 = Map("key1" -> "value1")map1: scala.collection.immutable.Map[String,String] = Map(key1 -> value1)scala> val value1 = map1.get("key1")value1: Option[String] = Some(value1)scala> val value2 = map1.get("key2")value2: Option[String] = Nonescala> def printContentLength(x: Option[String]) {   |  for (c <- x){   |   println(c.length)   |  }   | }printContentLength: (x: Option[String])Unitscala> printContentLength(value1)6scala> printContentLength(value2)

map 操作

在函数式编程中有个核心的概念之一是转换 所以大部份支持函数式编程语言 都支持一种叫 map() 的动作 这个动作是可以帮你把某个容器的内容 套上一些动作之后 变成另个新的容器。

现在 考虑如何用 Option 的 map 方法实现 length: xxx 的输出形式:

先算出 Option 容器内字符串的长度

然后在长度前面加上 "length: " 字样

最后把容器走访一次 印出容器内的东西

scala> value1.map(_.length).map("length: " + _).foreach(println)length: 6scala> value1.map("length: " + _.length).foreach(println)length: 6

透过这样「转换」的方法 一样可以达成想要的效果 而且同样不用去做「是否为 None」的判断。

原文链接:https://www.jianshu.com/p/95896d06a94d