在 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之前,代码如果要表示某个值不存在,一般不得不返回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"))
来赋予默认值。
在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
}