如何避免使用null?

时间:2014-04-22 15:22:23

标签: scala

假设我的域名对象名为" Office"

case class Office(
   id: Long,
   name: String,
   phone: String,
   address: String
) {
    def this(name: String, phone: String, address: String) = this(
        null.asInstanceOf[Long], name, phone, address
    )
}

当我创建新的 Office 时:

new Office("officeName","00000000000", "officeAddress")

我没有指定 id 字段,因为我不知道。当我保存办公室时(通过Anorm)我现在 id 并执行此操作:

office.id = officeId

因此。我知道使用 null 是非Scala方式。如何避免在我的情况下使用 null

更新#1

使用选项

假设,像这样:

case class Office(
   id: Option[Long],
   name: String,
   phone: String,
   address: String
) {
    def this(name: String, phone: String, address: String) = this(
        None, name, phone, address
    )
}

并且,保存后:

office.id = Option(officeId)

但是如果我需要通过办公室ID找到什么呢?

SomeService.findSomethingByOfficeId(office.id.get)

清楚吗? office.id.get 看起来不那么好)

更新#2

每个人都要感谢!我从你的答案中得到了新的想法!致谢谢谢!

4 个答案:

答案 0 :(得分:4)

为什么不将id字段声明为Option?你应该避免在Scala中使用null。 Option是首选,因为它是类型安全的,并且在功能范例中与其他结构一起使用时很好。

像(我没有测试过这段代码):

case class Office(
   id: Option[Long],
   name: String,
   phone: String,
   address: String
) {
    def this(name: String, phone: String, address: String) = this(
        None, name, phone, address
    )
}

答案 1 :(得分:2)

只需将id字段设为Option[Long];一旦你拥有了它,就可以像这样使用它:

office.id.map(SomeService.findSomethingByOfficeId)

这将执行您想要的操作并返回Option[Something]。如果office.idNone,则map()甚至不会调用finder方法,并会立即返回None,这通常是您想要的。

如果findSomethingByOfficeId返回Option[Something](它应该),而不仅仅是Somethingnull /例外,请使用:

office.id.flatMap(SomeService.findSomethingByOfficeId)

这样,如果office.idNone,它将再次立即返回None;但是,如果它是Some(123),它会将123传递给findSomethingByOfficeId;现在,如果查找程序返回Some(something),它将返回Some(something),如果查找程序返回None,它将再次返回None

如果findSomethingByOfficeId可以返回null并且您无法更改其源代码,请使用Option(...)对其进行任何调用 - 这会将null转换为None并将Some(...)中的任何其他值换行;如果它在找不到某个内容时可以抛出异常,请用Try(...).toOption包装对它的调用以获得相同的效果(尽管这也会将任何不相关的异常转换为None,这是可能不受欢迎,但您可以使用recoverWith修复此问题。

一般准则总是避免null和Scala代码中的例外(如您所述);始终更喜欢Option[T]mapflatMap链接,或使用monadic for句法糖隐藏mapflatMap的使用。< / p>

可运行的示例:

object OptionDemo extends App {
  case class Something(name: String)
  case class Office(id: Option[Long])

  def findSomethingByOfficeId(officeId: Long) = {
    if (officeId == 123) Some(Something("London")) else None
  }

  val office1 = Office(id = None)
  val office2 = Office(id = Some(456))
  val office3 = Office(id = Some(123))

  println(office1.id.flatMap(findSomethingByOfficeId))
  println(office2.id.flatMap(findSomethingByOfficeId))
  println(office3.id.flatMap(findSomethingByOfficeId))
}

<强>输出:

None
None
Some(Something(London))

有关Scala非常有用的Option[T]类型的精彩介绍,请参阅http://danielwestheide.com/blog/2012/12/19/the-neophytes-guide-to-scala-part-5-the-option-type.html

答案 2 :(得分:0)

使用id: Option[Long]时,使用

提取选项值
if (office.id.isDefined) {
  val Some(id) = office.id
  SomeService.findSomethingByOfficeId(id)
}

或者可能是例如

office.id match {
  case None => Array()
  case Some(id) => SomeService.findSomethingByOfficeId(id)
}

您还可以按如下方式定义案例类和对象,

trait OId
case object NoId extends OId
case class Id(value: Long) extends OId

case class Office (
   id: OId = NoId,
   name: String,
   phone: String,
   address: String
)

请注意,默认idNoId,无需声明对this的调用。然后

val office = Office (Id(123), "name","phone","addr")
val officeNoId = Office (name = "name",phone="phone",address="addr")

如果最后定义了id成员,则无需为成员名称命名,

val office = Office ("name","phone","addr")
office: Office = Office(name,phone,addr,NoId)

在调用(整齐地)方法时,

office.id match {
  case NoId => Array()
  case Id(value) => SomeService.findSomethingByOfficeId(value)
}

答案 3 :(得分:-1)

我更喜欢对象 Id 属性的强烈限制:

trait Id[+T] {
  class ObjectHasNoIdExcepthion extends Throwable
  def id : T = throw new ObjectHasNoIdExcepthion
}

case class Office(
   name: String,
   phone: String,
   address: String
) extends Id[Long]

object Office {
  def apply(_id : Long, name: String, phone: String, address: String) = 
    new Office(name, phone, address) {
      override def id : Long = _id
    }
} 

如果我尝试为对象获取未存储在DB中的Id,我会得到异常,这意味着程序行为有问题。

val officeWithoutId = 
  Office("officeName","00000000000", "officeAddress")

officeWithoutId.id // Throw exception

// for-comprehension and Try monad can be used
// update office:
for {
  id <- Try { officeWithoutId.id }
  newOffice = officeWithoutId.copy(name = "newOffice")
} yield OfficeDAL.update(id, newOffice)

// find office by id:
for {
  id <- Try { officeWithoutId.id }
} yield OfficeDAL.findById(id)


val officeWithId    = 
  Office(1000L, "officeName","00000000000", "officeAddress")

officeWithId.id // Ok

<强>优点:

1)使用 id 参数的方法可以在DAL逻辑中封装

private[dal] def apply (_id : Long, name: String, ...

2)复制方法始终创建空 id 的新对象(如果更改数据则为安全)

3)更新方法是安全的(默认情况下不会覆盖对象, id 总是需要指定)

<强>缺点:

1)商店 id 属性需要特殊的serealization / deserealization逻辑(json用于webservices等)

<强> P.S。 如果您有不可变对象(ADT)并将其存储到具有id +对象版本而非对象替换的DB中,则此方法很好。