4

在 Scala 中,如果我有

hub = myBicycle.getFrontWheel.getHub()

并且有可能缺少前轮,即myBicycle.getFrontWheel() == null,我只想在这种情况下hub被分配null,最简洁的表达方式是什么?

我目前不得不做

hub = if (myBicycle.getFrontWheel() == null) null else myBicycle.getFrontWheel.getHub()

当访问器链更长时,情况会变得更糟。

不熟悉 Scala 宏,我想知道是否可以编写一个 Scala 宏,以某种方式捕获方法名称并仅在对象引用非空时应用它?

4

3 回答 3

5

实际上,我最近写了一个这样的宏,灵感来自一个关于 Scala 中的 null-safe dereferences 的问题。顺便说一句,这个问题与这个问题非常相似,并且包含了一个关于你可以做些什么来实现这一目标的长时间讨论,包括 的用法Option、捕捉 NPE 的奇特方式等。

关于我写的宏的一些评论(来源包括在下面):

  • 它返回一个Option,它会None在某个时候有一个空取消引用。
  • 它使用 if-else 将每个成员访问通过 dot 转换为 null 保护访问,None当前缀为 . 时返回null
  • 它变得有点复杂,我想象它是......
  • 在某些极端情况下它不起作用,我知道的一个是使用getClass方法时 - 编译器根据其返回类型专门处理它。
  • 关于隐式转换存在潜在的不一致。想象一下,您有一个表达式a.bb通过隐式转换到达,因此有效地,该表达式类似于conv(a).b. 现在,问题出现了:我们应该检查anull或是conv(a)还是null两者兼而有之?目前,我的宏仅检查是否anull我来说似乎更自然一些,但在某些情况下这可能不是所需的行为。此外,在我的宏中检测隐式转换有点小技巧,在这个问题中有描述。

宏:

def withNullGuards[T](expr: T): Option[T] = macro withNullGuards_impl[T]

def withNullGuards_impl[T](c: Context)(expr: c.Expr[T]): c.Expr[Option[T]] = {
  import c.universe._

  def eqOp = newTermName("==").encodedName
  def nullTree = c.literalNull.tree
  def noneTree = reify(None).tree
  def someApplyTree = Select(reify(Some).tree, newTermName("apply"))

  def wrapInSome(tree: Tree) = Apply(someApplyTree, List(tree))

  def canBeNull(tree: Tree) = {
    val sym = tree.symbol
    val tpe = tree.tpe

    sym != null &&
      !sym.isModule && !sym.isModuleClass &&
      !sym.isPackage && !sym.isPackageClass &&
      !(tpe <:< typeOf[AnyVal])
  }

  def isInferredImplicitConversion(apply: Tree, fun: Tree, arg: Tree) =
    fun.symbol.isImplicit && (!apply.pos.isDefined || apply.pos == arg.pos)

  def nullGuarded(originalPrefix: Tree, prefixTree: Tree, whenNonNull: Tree => Tree): Tree =
    if (canBeNull(originalPrefix)) {
      val prefixVal = c.fresh()
      Block(
        ValDef(Modifiers(), prefixVal, TypeTree(null), prefixTree),
        If(
          Apply(Select(Ident(prefixVal), eqOp), List(nullTree)),
          noneTree,
          whenNonNull(Ident(prefixVal))
        )
      )
    } else whenNonNull(prefixTree)

  def addNullGuards(tree: Tree, whenNonNull: Tree => Tree): Tree = tree match {
    case Select(qualifier, name) =>
      addNullGuards(qualifier, guardedQualifier =>
        nullGuarded(qualifier, guardedQualifier, prefix => whenNonNull(Select(prefix, name))))
    case Apply(fun, List(arg)) if (isInferredImplicitConversion(tree, fun, arg)) =>
      addNullGuards(arg, guardedArg =>
        nullGuarded(arg, guardedArg, prefix => whenNonNull(Apply(fun, List(prefix)))))
    case Apply(Select(qualifier, name), args) =>
      addNullGuards(qualifier, guardedQualifier =>
        nullGuarded(qualifier, guardedQualifier, prefix => whenNonNull(Apply(Select(prefix, name), args))))
    case Apply(fun, args) =>
      addNullGuards(fun, guardedFun => whenNonNull(Apply(guardedFun, args)))
    case _ => whenNonNull(tree)
  }

  c.Expr[Option[T]](addNullGuards(expr.tree, tree => wrapInSome(tree)))
}

编辑

这是使语法更好的附加代码:

def any2question_impl[T, R >: T](c: Context {type PrefixType = any2question[T]})(default: c.Expr[R]): c.Expr[R] = {
  import c.universe._

  val Apply(_, List(prefix)) = c.prefix.tree
  val nullGuardedPrefix = withNullGuards_impl(c)(c.Expr[T](prefix))
  reify {
    nullGuardedPrefix.splice.getOrElse(default.splice)
  }
}

implicit class any2question[T](any: T) {
  def ?[R >: T](default: R): R = macro any2question_impl[T, R]
}

最后,你可以有这样的代码:

val str1: String = "hovercraftfullofeels"
val result1 = str1.substring(3).toUpperCase ? "THERE WAS NULL"
println(result1) // prints "ERCRAFTFULLOFEELS"

val str2: String = null
val result2 = str2.substring(3).toUpperCase ? "THERE WAS NULL"
println(result2) // prints "THERE WAS NULL"
于 2013-04-03T10:45:15.763 回答
4

除非您必须与固定的 Java 代码进行互操作,否则您应该使用Option而不是null; 因此getFrontWheel()会返回Option[Wheel],然后您可以使用map/flatMap沿着链条向下:

val hub:Option[Hub] = myBicycle.getFrontWheel().flatMap(wheel => wheel.getHub())
val spoke:Option[Spoke] = myBicycle.getFrontWheel().flatMap(wheel => wheel.getHub().map(hub => hub.getSpoke()))

(假设hub.getSpoke()返回Spoke,不是Option[Spoke]

最后一个例子可以改写为

val spoke:Option[Spoke] =
  for (wheel <- myBicycle.getFrontWheel();
       hub <- wheel.getHub())
  yield hub.getSpoke()

如果您真的必须处理,null您可以通过以下方式轻松转换结果Option

val wheel:Option[Wheel] = Option(myBicycle.getFrontWheel())
于 2013-04-02T23:26:16.613 回答
0

你可以这样写:

def nil[B >: Null] (fun : => B) : B = {
  try fun
  catch {case e : NullPointerException => null}
}

然后像这样使用它:

val hub = nil(myBicycle.getFrontWheel.getHub)

不过,它也会影响嵌套调用的空异常。如果您想按照前面提到的方式编写它,请至少这样做:

val hub = Option(myBicycle.getFrontWheel()).map(_.getHub).getOrElse(null)
于 2013-04-03T01:51:52.567 回答