类选项[T]的意义何在?

我无法理解Scala的课程要点。我的意思是,我看不到任何优势。Option[T]Nonenull

例如,考虑以下代码:

object Main{
  class Person(name: String, var age: int){
    def display = println(name+" "+age)
  }

  def getPerson1: Person = {
    // returns a Person instance or null
  }

  def getPerson2: Option[Person] = {
    // returns either Some[Person] or None
  }

  def main(argv: Array[String]): Unit = {
    val p = getPerson1
    if (p!=null) p.display

    getPerson2 match{
      case Some(person) => person.display
      case None => /* Do nothing */
    }
  }
}

现在假设,该方法返回 ,则对 的第一行进行的调用注定会失败,出现 。同样,如果返回,调用将再次失败,并出现类似的错误。getPerson1nulldisplaymainNPEgetPerson2Nonedisplay

如果是这样,那么为什么Scala通过引入新的值包装器()而不是遵循Java中使用的简单方法使事情复杂化?Option[T]

更新:

我已经按照@Mitch的建议编辑了我的代码。我仍然看不到任何特别的优势。我必须测试特殊情况或两种情况。:(Option[T]nullNone

如果我从@Michael的回复中正确理解,那么唯一的好处是它明确告诉程序员此方法可以返回None吗?这是这种设计选择背后的唯一原因吗?Option[T]


答案 1

如果你强迫自己永远不要使用.,你就会变得更好。这是因为等同于“好吧,把我送回空地”。Optiongetget

所以,以你的例子为例。如果不使用,您将如何呼叫?以下是一些替代方案:displayget

getPerson2 foreach (_.display)
for (person <- getPerson2) person.display
getPerson2 match {
  case Some(person) => person.display
  case _ =>
}
getPerson2.getOrElse(Person("Unknown", 0)).display

这些替代方案都不会让你调用不存在的东西。display

至于为什么存在,Scala不会告诉你你的代码应该如何编写。它可能会轻轻地刺激你,但如果你想回到没有安全网的地方,这是你的选择。get


你把它钉在这里:

Option[T]的唯一优点是它明确地告诉程序员这种方法可以返回None?

除了“唯一”。但让我以另一种方式重申这一点:over的主要优势是类型安全。它确保您不会将方法发送到可能不存在的对象,因为编译器不会让您这样做。Option[T]TT

你说你必须在这两种情况下测试可空性,但是如果你忘记了 - 或者不知道 - 你必须检查空值,编译器会告诉你吗?还是您的用户会这样做?

当然,由于它与Java的互操作性,Scala允许空值,就像Java一样。因此,如果您使用Java库,如果您使用编写不良的Scala库,或者如果您使用编写不良的个人Scala库,您仍然必须处理空指针。

我能想到的另外两个重要优点是:Option

  • 文档:方法类型签名将告诉您对象是否始终返回。

  • 一元可组合性。

后者需要更长的时间才能完全理解,并且它不太适合简单的示例,因为它只显示了它在复杂代码上的优势。所以,我将在下面举一个例子,但我很清楚,除了那些已经得到它的人之外,它几乎没有任何意义。

for {
  person <- getUsers
  email <- person.getEmail // Assuming getEmail returns Option[String]
} yield (person, email)

答案 2

比较:

val p = getPerson1 // a potentially null Person
val favouriteColour = if (p == null) p.favouriteColour else null

跟:

val p = getPerson2 // an Option[Person]
val favouriteColour = p.map(_.favouriteColour)

在 Scala 中作为映射函数出现的 monadic 属性绑定允许我们对对象进行链式操作,而不必担心它们是否为“空”。

再举一点这个简单的例子。假设我们想找到一个人物列表中所有最喜欢的颜色。

// list of (potentially null) Persons
for (person <- listOfPeople) yield if (person == null) null else person.favouriteColour

// list of Options[Person]
listOfPeople.map(_.map(_.favouriteColour))
listOfPeople.flatMap(_.map(_.favouriteColour)) // discards all None's

或者,也许我们想找到一个人的父亲的母亲的妹妹的名字:

// with potential nulls
val father = if (person == null) null else person.father
val mother = if (father == null) null else father.mother
val sister = if (mother == null) null else mother.sister

// with options
val fathersMothersSister = getPerson2.flatMap(_.father).flatMap(_.mother).flatMap(_.sister)

我希望这能揭示一些选择如何使生活更轻松。