Scala学习之旅-对Option友好的flatMap

聊点什么

  • Option
  • flatMap vs. Option

Option的作用

在 Java/Scala中, Optional/Option(本文还是以scala代码为例) 是用来表示某个对象存在或者不存在,也就是说, Option是某个类型 T的 Wrapper,

  • 如果 T != null, Option(T).isDefined == true
  • 如果 T == null, Option(T).isEmpty == true

有了Option这层金刚罩, null被包裹住了,应用代码不必直接处理它,也就极大避免了 NullPointerException出现的可能性。Java 11 document 如下:

the purpose of Optional is to provide a return type that can represent the absence of value in scenarios where returning null might cause unexpected errors, like the infamous NullPointerException.

为什么有了Option就能避免了NullPointerException? 且看

Option vs. null

在没有Option之前,代码如果要表示某个值不存在,一般不得不返回null, 但是应用对null的检查,是必要的, 但不是必须的, 所以稍有不慎,就会有NullPointerException发生。

有了Option之后, 是在编译期显示要求去 get Option内的 value, 这就给了应用处理不存在的机会. 请看如下示例:


private val optionSeq = Seq(
  Person("Apple", Some(1)),
  Person("Boy"),
  Person("Carlo", Some(2)),
  Person("Doggy"),
  Person("Egg"),
  Person("Frog", Some(3))
)

  /**
   * Option vs. null
   */
  private def findPersonByNameWithNull(name: String): Person = {
    for (p <- optionSeq) {
      if (p.name == name)
        return p
    }
    null
  }

  private def findPersonByNameWithOption(name: String): Option[Person] = {
    for (p <- optionSeq) {
      if (p.name == name)
        return Some(p)
    }
    None
  }

  try {
    findPersonByNameWithNull("NoName").name
  } catch {
    case e: NullPointerException => println(s"Got NullPointerException: ${e.getMessage}")
    case ex: Exception => println(ex)
  } // Got NullPointerException: Cannot invoke "ddu.scala.articles.FlatMapTips$Person.name()" because the return value of "ddu.scala.articles.FlatMapTips$.findPersonByNameWithNull(String)" is null

  println(findPersonByNameWithOption("NoName").getOrElse(Person(name = "NotFound")).name) // NotFound

在上面的例子中我们可以看到, 在应用调用 findPersonByNameWithOption("NoName")得到Option之后,应用有机会处理OptiongetOrElse(Person(name = "NotFound"))来赋予默认值。

flatMap vs. Option

在Scala中, 特别是在流式编程中, Option被用的越来越多了, Option虽然可以避免NullPointerException,但是由于需要应用代码显示处理,大量的match ... case... or get ... else...
也是非常繁琐。让我们看看Scala的flatMap是如果友好得处理Option的吧。

  private val optionSeq = Seq(
    Person("Apple", Some(1)),
    Person("Boy"),
    Person("Carlo", Some(2)),
    Person("Doggy"),
    Person("Egg"),
    Person("Frog", Some(3))
  )

  /**
   * Tip.1 flatMap 会忽略掉 None
   *
   * 下面两段代码是等价的都输出 123
   *  显然用 flatMap 更加简洁
   */

  optionSeq
    .flatMap(_.age)
    .foreach(println)

  optionSeq
    .map(_.age)
    .filter(_.isDefined)
    .map(_.get)
    .foreach(println)


  /**
   * Tip.2 flatMap 对 Option 对象相当于 match case
   *
   * 输出示例:
   * flatMapName: GOD, matchCaseName: GOD
   * flatMapName: NO_NAME, matchCaseName: NO_NAME
   *
   */

  private def theName(name: Option[String]): Unit = {
    val flatMapName = name.flatMap(name => Some(name.toUpperCase())).getOrElse("NO_NAME")
    val matchCaseName = name match {
      case Some(name) => name.toUpperCase
      case _ => "NO_NAME"
    }
    println(s"flatMapName: $flatMapName, matchCaseName: $matchCaseName")
  }

  theName(Some("God"))
  theName(None)

完整示例代码


package ddu.scala.articles

object FlatMapTips extends App {

  case class Person(name: String, age: Option[Int] = None)

  private val optionSeq = Seq(
    Person("Apple", Some(1)),
    Person("Boy"),
    Person("Carlo", Some(2)),
    Person("Doggy"),
    Person("Egg"),
    Person("Frog", Some(3))
  )

  /**
   * Tip.1 flatMap 会忽略掉 None
   *
   * 下面两段代码是等价的都输出 123
   *  显然用 flatMap 更加简洁
   */

  optionSeq
    .flatMap(_.age)
    .foreach(println)

  optionSeq
    .map(_.age)
    .filter(_.isDefined)
    .map(_.get)
    .foreach(println)


  /**
   * Tip.2 flatMap 对 Option 对象相当于 match case
   *
   * 输出示例:
   * flatMapName: GOD, matchCaseName: GOD
   * flatMapName: NO_NAME, matchCaseName: NO_NAME
   *
   */

  private def theName(name: Option[String]): Unit = {
    val flatMapName = name.flatMap(name => Some(name.toUpperCase())).getOrElse("NO_NAME")
    val matchCaseName = name match {
      case Some(name) => name.toUpperCase
      case _ => "NO_NAME"
    }
    println(s"flatMapName: $flatMapName, matchCaseName: $matchCaseName")
  }

  theName(Some("God"))
  theName(None)

  /**
   * Option vs. null
   */
  private def findPersonByNameWithNull(name: String): Person = {
    for (p <- optionSeq) {
      if (p.name == name)
        return p
    }
    null
  }

  private def findPersonByNameWithOption(name: String): Option[Person] = {
    for (p <- optionSeq) {
      if (p.name == name)
        return Some(p)
    }
    None
  }

  try {
    findPersonByNameWithNull("NoName").name
  } catch {
    case e: NullPointerException => println(s"Got NullPointerException: ${e.getMessage}")
    case ex: Exception => println(ex)
  } // Got NullPointerException: Cannot invoke "ddu.scala.articles.FlatMapTips$Person.name()" because the return value of "ddu.scala.articles.FlatMapTips$.findPersonByNameWithNull(String)" is null

  println(findPersonByNameWithOption("NoName").getOrElse(Person(name = "NotFound")).name) // NotFound
  println(findPersonByNameWithOption("NoName").get.name) // NotFound
}

你可能感兴趣的:(scala,学习)