Java 类选项[T]的意义是什么?

Java 类选项[T]的意义是什么?,java,scala,null,functional-programming,monads,Java,Scala,Null,Functional Programming,Monads,我无法理解Scala中的Option[T]类的意义。我的意思是,我看不出None比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 getPers

我无法理解Scala中的
Option[T]
类的意义。我的意思是,我看不出
None
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
,那么对
main
第一行上的
display
的调用肯定会失败,因为
NPE
。类似地,如果
getPerson2
返回
None
,则
显示
调用将再次失败,并出现类似错误

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

更新:

我已根据的建议编辑了我的代码。我仍然看不到
选项[T]
的任何特殊优势。我必须在这两种情况下测试异常
null
None
:(


如果我从中正确理解,
选项[T]的唯一优点是什么
是它明确地告诉程序员这个方法可以不返回任何值吗?这是这个设计选择背后的唯一原因吗?

差别很微妙。请记住,作为一个真正的函数,它必须返回一个值-null实际上不被认为是“正常的返回值”,从这个意义上说,更像是一个“无”

但是,从实际意义上讲,当您调用一个可选返回某些内容的函数时,您将执行以下操作:

getPerson2 match {
   case Some(person) => //handle a person
   case None => //handle nothing 
}
诚然,您可以使用null执行类似的操作,但这使得调用
getPerson2
的语义变得显而易见,因为它返回
Option[Person]
(这是一件非常实用的事情,而不是依赖于某个人阅读文档并获得NPE,因为他们不阅读文档)


我将尝试挖掘出一个函数式程序员,他能给出比我更严格的答案。

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

事先承认这是一个油嘴滑舌的答案,选项是一个单子。

在Randall的基础上添加,理解为什么可能缺少value由
Option
表示,需要了解
Option
与Scala中许多其他类型共享的内容,特别是建模单子的类型。如果其中一个表示不存在null值,则该不存在区分不能参与其他单子类型共享的契约

如果您不知道monad是什么,或者您没有注意到它们在Scala的库中是如何表示的,那么您将看不到
选项
的作用,也看不到您遗漏了什么。即使没有任何monad概念,使用
选项
而不是null也有很多好处,这一点值得注意(我在“选项成本/some vs null”scala用户邮件列表线程中讨论了其中一些问题),但谈论it隔离有点像谈论一个特定链表实现的迭代器类型,想知道为什么它是必要的,同时忽略了更通用的容器/迭代器/算法接口。这里也有一个更广泛的接口,并且
选项
提供了在接口处。

比较:

val p = getPerson1 // a potentially null Person
val favouriteColour = if (p == null) p.favouriteColour else null
File f = (filename==null) ? null : new File(filename);
val f = filename map (new File(_))
与:

一元属性绑定(在Scala中作为映射函数出现)允许我们在对象上链接操作,而不必担心它们是否为“null”

再举一个简单的例子,假设我们想找到一系列人最喜欢的颜色

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

我希望这能让你明白选项如何让生活变得更轻松。

如果你强迫自己永远不要使用
get
,你会更好地理解
选项的意义。这是因为
get
相当于“好吧,把我送回空域”

那么,以您的示例为例。如果不使用
get
,您将如何调用
display
?以下是一些备选方案:

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

对于我来说,使用For comprehension语法时,选项非常有趣。以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
//可能为空
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
//有选择权
val fathersMothersSister=for{

父它不是用来帮助避免空检查的,它是用来强制空检查的。当你的类有10个字段,其中两个字段可能为空时,问题就变得很清楚了。你的系统还有50个其他类似的类。在Java世界中,你试图使用某种精神力量、命名约定或可能的组合来阻止这些字段上的NPE即使是注释,每个Java开发人员都会在很大程度上失败。Option类不仅使“nullable”任何试图理解代码的开发人员都可以清楚地看到这些值,但允许编译器强制执行这一之前未提及的契约。

我认为关键在于Synesso的答案:Option主要不是一个繁琐的null别名,而是一个可以帮助您完成逻辑的完整对象

null的问题在于缺少对象。它没有任何方法可以帮助您处理它(尽管作为语言设计师,如果您真的喜欢,您可以向您的语言添加越来越长的功能列表,以模拟对象)

正如您所演示的,Option可以做的一件事是模拟null;然后您必须测试异常值“None”而不是异常值“null”。如果您忘记了,在任何一种情况下,坏事情都会发生。Option确实降低了意外发生的可能性,因为您必须键入“get”(这应该提醒你,它可能是空的,呃,我的意思是没有),但这是一个额外的wra交换的小好处
for {
  person <- getUsers
  email <- person.getEmail // Assuming getEmail returns Option[String]
} yield (person, email)
// 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
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(_))
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.")
}
def nullableMap[T](value: T, f: T => T) = if (value == null) null else f(value)
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") )
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
val value = for {
row <- database fetchRowById 42
key <- row get "port_key"
value <- myMap get key
} yield value
val result = value getOrElse defaultValue
if (x == null) ...
else x.foo()
x match {
case None => ...
case Some(y) => y.foo
}