什么是类的选项?

我无法理解Scala中的Option[T]类。 我的意思是,我无法看到任何超过null

例如,考虑下面的代码:

 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 */ } } } 

现在假设getPerson1方法返回null ,那么在NPE第一行display的调用就会失败。 同样,如果getPerson2返回None ,则display调用将再次失败并出现类似的错误。

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

更新:

我根据@Mitch的build议编辑了我的代码。 我仍然无法看到Option[T]任何特别的优点。 在这两种情况下,我必须testing特殊的nullNone 。 🙁

如果我从@Michael的回答中得到了正确的理解, Option[T]的唯一优点是它明确告诉程序员这个方法可以返回None吗? 这是deviseselect背后的唯一原因吗?

如果你强迫自己永远不会使用get ,那么你会更好地Option 。 那是因为get相当于“好吧,把我送回空地”。

所以,以你的例子。 如何在不使用get情况下调用display ? 这里有一些select:

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

这些替代品都不会让你在不存在的东西上display

至于为什么get存在,斯卡拉不会告诉你如何写你的代码。 它可能轻轻刺激你,但如果你想回落到没有安全网,这是你的select。


你钉在这里:

Option [T]的唯一好处是它明确告诉程序员这个方法可以返回None吗?

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

你说你必须在两种情况下都testing可空性,但如果你忘了 – 或者不知道 – 你必须检查null,编译器会告诉你吗? 或者将你的用户?

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

Option I的其他两个重要优点可以考虑的是:

  • 文档:方法types签名会告诉你一个对象是否总是被返回。

  • Monadic可组合性。

后者需要更长的时间才能充分理解,并不适合于简单的例子,因为它只能显示复杂代码的优势。 所以,我将在下面举一个例子,但是我很清楚,除了那些已经获得它的人以外,这并不意味着什么。

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

比较:

 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) 

monadic属性绑定 ,它出现在Scala中作为map函数,允许我们在对象上链接操作,而不用担心它们是否为“null”。

稍微进一步讲一下这个简单的例子。 假设我们想find所有人喜欢的颜色。

 // 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 

也许我们想find一个人的父亲的母亲的妹妹的名字:

 // 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) 

我希望这可以让我们了解一些select如何让生活变得更轻松。

差别是微妙的。 记住要成为一个真正的函数,它必须返回一个值 – 在这个意义上,null并不真正被认为是一个“正常的返回值”,更多的是一个底部types /什么也不是。

但是,从实际意义上讲,当你调用一个可选的返回值时,你可以这样做:

 getPerson2 match { case Some(person) => //handle a person case None => //handle nothing } 

当然,你可以用null来做类似的事情 – 但是这使得调用getPerson2的语义显而易见,因为它返回Option[Person] (这是一个很好的实际的东西,除了依靠某人阅读文档并获得NPE,因为他们不要阅读文档)。

我会尽力挖掘一个function强大的程序员,他能给出比我更严格的答案。

对于我来说,用理解语法来处理选项是非常有趣的。 以synesso为例:

 // 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 = for { father <- person.father mother <- father.mother sister <- mother.sister } yield sister 

如果任何fathersMothersSisterNone ,那么fathersMothersSister将是None但不会引发NullPointerException 。 然后,您可以安全地将fathersMothersSister传递给采用Option参数的函数,而无需担心。 所以你不检查null,你不关心的例外。 将它与synesso示例中提供的java版本进行比较。

你有非常强大的select组成function:

 def getURL : Option[URL] def getDefaultURL : Option[URL] val (host,port) = (getURL orElse getDefaultURL).map( url => (url.getHost,url.getPort) ).getOrElse( throw new IllegalStateException("No URL defined") ) 

也许有人指出这一点,但我没有看到它:

与Option [T]与空检查模式匹配的一个优点是Option是一个密封的类,所以如果忽略代码Some或None的情况下,Scala编译器会发出警告。 编译器有一个编译器标志,会将警告转化为错误。 所以可以避免在编译时而不是在运行时处理“不存在”的情况。 与使用空值相比,这是一个巨大的优势。

这不是在帮助避免一个空检查,它是在那里强制空检查。 当你的class级有10个字段,其中两个可能为空时,这一点就变得清晰了。 而你的系统有50个其他类似的类。 在Java世界里,你试图通过脑力活动,命名约定或者甚至注释的组合来防止那些领域的NPE。 而且每个Java开发人员在这方面都失败了很多。 Option类不仅让任何试图理解代码的开发人员清楚地看到“可空”值,而且允许编译器强制执行这个以前没有说出来的合同。

[由Daniel Spiewak的 评论复制]

如果使用Option的唯一方法是模式匹配,以获得值,那么是的,我同意它不会改善超过null。 但是,你错过了一大类function。 使用Option的唯一有说服力的理由是如果你使用它的高阶实用function。 有效地,你需要使用它的monadic性质。 例如(假设一定数量的API修剪):

 val row: Option[Row] = database fetchRowById 42 val key: Option[String] = row flatMap { _ get “port_key” } val value: Option[MyType] = key flatMap (myMap get) val result: MyType = value getOrElse defaultValue 

那里,这不是很漂亮吗? 如果我们使用-comprehensions,我们实际上可以做得更好:

 val value = for { row <- database fetchRowById 42 key <- row get "port_key" value <- myMap get key } yield value val result = value getOrElse defaultValue 

你会注意到我们从来没有明确地检查null,None或者其他类似的结果。 期权的全部是避免任何检查。 你只需要计算一些string,然后向下移动,直到你真的需要得到一个值。 此时,您可以决定是否要进行显式检查(您不应该这样做),提供默认值,抛出exception等。

我从来没有做过任何明确的匹配Option ,我知道很多其他的Scala开发者在同一条船上。 大卫·波拉克(David Pollak)前几天向我提到,他在Option (或者在Box的情况下,在Lift的情况下)使用了这样的显式匹配,表示编写代码的开发人员不完全理解语言及其标准库。

我不是要成为一个巨魔锤子,但是你真的需要看看语言特征在实践中如何被实际使用,然后再把它们打成无用的。 我绝对同意,Option是相当不可靠的,因为你使用它,但是你没有按照它的devise方式使用它。

有一点似乎没有人在这里提出的是,虽然你可以有一个空引用,有一个区别引入的选项。

也就是说,你可以有Option[Option[A]] ,其中Some(None)Some(Some(a)) ,其中aA的常用居民之一。 这意味着如果你有某种types的容器,并且希望能够存储空指针,并把它们取出来,你需要传回一些额外的布尔值来知道你是否真的得到了一个值。 像这样的瑕疵在java容器API中比比皆是 ,一些无锁变体甚至不能提供它们。

null是一次性构造,它不是自己构成的,只能用于引用types,并且迫使你以非全面的方式进行推理。

例如,当你检查

 if (x == null) ... else x.foo() 

你必须在整个else分支中携带x != null ,并且这已经被检查过了。 但是,使用类似选项时

 x match { case None => ... case Some(y) => y.foo } 

如果不是由于霍尔的十亿美元的错误 ,你知道你的build造并不是None – 而且你也知道它也不是null

Option [T]是一个monad,当你使用高阶函数来操作数值的时候,它是非常有用的。

我build议你阅读下面列出的文章,他们是很好的文章,告诉你为什么选项[T]是有用的,它如何在function上使用。

  • 火星人对Monad:无视为有害的
  • Monads是大象第1部分

再加上Randall 提供的答案 ,了解为什么潜在的价值缺失是由Option来表示的,需要了解哪些Option与Scala中的许多其他types共享,尤其是typesbuild模monad。 如果一个表示没有null的值,那么缺席存在区别就不能参与其他一元types共享的契约。

如果你不知道单子是什么,或者你没有注意到它们是如何在Scala的图书馆中呈现出来的,那么你就不会看到Option跟着玩什么,而且你也看不到你错过了什么。 使用Option而不是null值有很多好处,即使在没有monad概念的情况下也是值得注意的(我在这里讨论其中的一些“选项/一些vs null” scala用户邮件列表线程),但是谈话关于它的隔离有点像谈论一个特定的链表实现的迭代器types,想知道为什么它是必需的,一直遗漏在更一般的容器/迭代器/algorithm接口上。 这里也有一个更广泛的接口,而Option提供了该接口的存在和不存在模型。

我认为在Synesso的答案中可以find关键:Option作为null的一个麻烦的别名主要不是有用的,而是作为一个完整的对象,可以用你的逻辑来帮助你。

null的问题是它缺less一个对象。 它没有任何方法可以帮助你处理它(尽pipe作为一个语言devise者,如果你真的喜欢它,你可以为你的语言添加越来越多的function列表,模仿一个对象)。

正如你所说的,Option可以做的一件事是模拟null; 那么你必须testing非常值“无”,而不是非常值“空”。 如果你忘记了,无论如何,坏事都会发生。 选项确实使它不太可能偶然发生,因为你必须键入“get”(它应该提醒你,它可能是空的,呃,我的意思是没有),但这是一个小的好处,换取额外的包装对象。

如果选项真的开始显示出它的力量,那么它将帮助你处理我想要的东西 – 但是我不能实际拥有一个。

让我们考虑一些你可能想要做的事情,可能是空的东西。

也许你想要设置一个默认值,如果你有一个null。 让我们来比较Java和Scala:

 String s = (input==null) ? "(undefined)" : input; val s = input getOrElse "(undefined)" 

我们有一个方法来处理“如果我是空的情况下使用默认值”的方法,那么我们可以使用一个比较麻烦的?:构造。 这会稍微清理一下你的代码。

也许你只是想要创造一个新的对象,如果你有一个真正的价值。 比较:

 File f = (filename==null) ? null : new File(filename); val f = filename map (new File(_)) 

斯卡拉略短,再次避免了错误的来源。 那么当你需要把事情联系在一起的时候,请考虑一下累积的好处,正如Synesso,Daniel和范例所示。

这并不是一个巨大的改进,但是如果你把所有的东西都加了起来,那么除了创buildSome(x)包装器对象的细小开销之外,还要保存非常高性能的代码(在这里你要避免这种情况)。

除了作为提醒你关于null / None情况的设备之外,匹配使用并不是真正有用的。 当真正有用的是当你开始链接时,例如,如果你有一个选项列表:

 val a = List(Some("Hi"),None,Some("Bye")); a match { case List(Some(x),_*) => println("We started with " + x) case _ => println("Nothing to start with.") } 

现在,您可以将“无”情况和“空列表”情况一起放在一个方便的语句中,该语句可以精确地提取所需的值。

这真是一个编程风格的问题。 使用Functional Java或者编写自己的帮助器方法,可以获得选项function,但不会放弃Java语言:

http://functionaljava.org/examples/#Option.bind

只是因为Scala默认包含它并不是特别的。 函数式语言的大多数方面都可以在该库中使用,并且可以与其他Java代码很好地共存。 就像你可以select用空值来编程Scala一样,你可以select不用它们来编程Java。

空返回值仅用于与Java兼容。 否则不应该使用它们。

事先承认这是一个口齿不清的答案,Option是一个monad。

其实我跟你有一个疑问 关于选项它真的困扰我,1)有一个性能开销,因为有一些“一些”包装创造everywehre。 2)我必须在我的代码中使用很多的Some和Option。

所以要看这个语言devise决定的优点和缺点,我们应该考虑替代scheme。 由于Java只是忽略了可空性的问题,所以不是一种替代scheme。 实际的替代scheme提供了Fantom编程语言。 那里有可以为空和不可空的types? ?:运算符而不是Scala的map / flatMap / getOrElse。 我在比较中看到下面的项目符号:

期权的优势:

  1. 更简单的语言 – 不需要额外的语言结构
  2. 与其他一元types一致

可空的优点:

  1. 在典型情况下更短的语法
  2. 更好的性能(因为您不需要为map,flatMap创build新的Option对象和lambdaexpression式)

所以这里没有明显的赢家。 还有一个笔记。 使用Option没有主要的语法优势。 你可以定义如下的东西:

 def nullableMap[T](value: T, f: T => T) = if (value == null) null else f(value) 

或者使用一些隐式转换来获得带点的私有语法。

具有显式选项types的真正优点是,您不能在所有地方的98%中使用它们,从而静态地排除空例外。 (另外2%的types系统会提醒你在实际访问时检查是否正确)

Option的另一种情况是在types不能有空值的情况下。 在Int,Float,Double等值中不能存储null,但是使用Option可以使用None。

在Java中,您将需要使用这些types的盒装版本(整数,…)。