何时使用Option

时间:2012-03-14 08:41:42

标签: scala

我学习Scala已有一段时间了,无法清楚地了解Option的用法。当我链接函数时,它可以帮助我避免空值检查(根据docs)。这对我来说很清楚:))

接下来我看到Option可以作为开发人员的一种指示器,在这里可以使用null值并且必须处理它。这是真的吗?如果是,我应该使用选项吗?例如

class Racer {
    val car = new Car()
}

我有Racer类,我确定car field不能为null(因为它是常量并在创建Racer实例时得到一个值)。这里不需要选项。

class Racer {
    var car = new Car()
}

我在这里制作汽车可以改变。并且有人可以为汽车分配零。我应该在这里使用Option吗?如果是,我注意到我所有的班级字段都是Option的候选者。我的代码看起来像这样

class Racer {
    var car: Option[Car] = None
    var currentRace: Option[Race] = None
    var team: Option[Team] = None
    ...
}

看起来不错吗?对我来说,似乎有点过度使用。

def foo(): Result = {
    if( something )
        new Result()
    else
        null
}

我有一个可以返回null的方法。我应该返回Option吗?如果方法可以返回null,我是否应该总是这样做?

任何有关它的想法都会有所帮助。提前谢谢!

我的问题与Why option类似,但我认为不一样。它更多地是关于的时候,而不是为什么。:)

4 个答案:

答案 0 :(得分:37)

您应该尽可能避免在Scala中null。它实际上只存在与Java的互操作性。因此,只要有可能函数或方法可以返回“无值”,或者逻辑上有效的成员变量具有“无值”,就可以使用null而不是Option

关于您的Racer示例:如果Racer没有carcurrentRaceteamnull是否真的有效?如果没有,那么你不应该选择那些成员变量。不要只选择它们,因为理论上可以将它们分配给Racer;只有在逻辑上你认为它是一个有效的null对象时才会这样做,如果这些成员变量中的任何一个没有值。

换句话说,最好假装null不存在。在Scala代码中使用def foo(): Option[Result] = if (something) Some(new Result()) else None 是代码气味。

Option

请注意,val opt = foo() // You can use pattern matching opt match { case Some(result) => println("The result is: " + result) case None => println("There was no result!") } // Or use for example foreach opt foreach { result => println("The result is: " + result) } 有许多有用的方法可供使用。

var

另外,如果你想用函数式编程,你应该尽可能地避免使用可变数据,这意味着:避免使用val,而是使用{{1}},使用不可变集合和尽可能使自己的类不可变。

答案 1 :(得分:10)

在Scala中进行函数式编程时,Optionnull更可取,因为它是类型安全的,并且在函数范例中与其他结构一起使用时很好。

特别是,您可以使用Option上的高阶函数轻松编写惯用代码。这个Scala Option Cheat Sheet对这个主题很有帮助。

答案 2 :(得分:8)

虽然Option可以使您的类定义看起来冗长,但另一种方法是不知道何时需要测试是否定义了变量。我认为在你的例子中,如果你的类是不可变的(所有字段都是val s),你就不需要这么多Option

对于方法foo,如果返回null,则需要记录它,客户端需要阅读该文档。然后客户端会编写一堆代码,如:

val result = foo(x)
if (result != null) {
  println(result)
}

如果您定义foo以返回Option[Result],则类型系统会强制客户端处理未定义结果的可能性。他们也拥有收藏类的全部力量。

result foreach println

使用带有Option的集合方法而不是测试null的另一个好处是,如果您的方法扩展为多元素集合(例如List) ,您可能根本不需要更改代码。例如,您最初可能认为您的Racer只能有一个工作人员,因此您定义val crew: Option[Crew]并且您可以使用crew.forall(_.age > 30).getOrElse(false)测试工作人员是否超过30岁。现在,如果您将定义更改为val crew: List[Crew],您的旧代码仍会编译,现在您将检查所有机组成员是否超过30个。

有时您需要处理null,因为您使用的库可能会返回它。例如,当您获得资源时,可能会得到null结果。幸运的是,很容易防御性地包装结果,因此它们可以转化为一种选择。

val resource = Option(this.getClass.getResource("foo"))

如果getResource返回null,则resource等于None,否则为Some[URL]。酷!

不幸的是,Option可能会有一些开销,因为它涉及额外的对象创建。但是,与空指针异常相比,这种开销很小!

答案 3 :(得分:6)

Options背后的想法是摆脱null,所以是的,你应该使用它们而不是返回“null或值”。这也是为什么,如果你想建模可选字段(0..1与其他对象的关系),使用Option绝对是一件好事。

如你所提到的那样,它的一个小缺点就是它使得很多可选字段的类声明变得有点冗长。

还有一件事,在scala中,我们鼓励你使用“不可变对象”,所以在你的例子中,字段应该是val个。 ;)