在Scala中,如果我有
hub = myBicycle.getFrontWheel.getHub()
并且可能缺少前轮,即myBicycle.getFrontWheel() == null
,我只想在这种情况下hub
分配null
,最简洁的表达方式是什么是什么?
我目前要做的事情
hub = if (myBicycle.getFrontWheel() == null) null else myBicycle.getFrontWheel.getHub()
当访问者链更长时,情况会变得更糟。
不熟悉Scala宏,我想知道是否有可能编写一个Scala宏以某种方式捕获方法名称并仅在对象引用为非null时才应用它?
答案 0 :(得分:5)
实际上,我最近写的是一个受a question about null-safe dereferences in Scala启发的宏。顺便说一下,这是与此问题非常类似的问题,并且包含了一个关于你可以做些什么来实现这一目标的长期讨论,包括Option
的使用,捕捉NPE的奇特方法等。
我写过一些关于宏的评论(源代码如下):
Option
,当某个时候出现空取消引用时,它会为None
。None
时返回null
。getClass
方法 - 这是由编译器专门针对其返回类型处理的。a.b
,并且b
是通过隐式转换达到的,所以有效地说,这个表达式就像conv(a).b
。现在,问题出现了:我们应该检查a
是null
还是conv(a)
是null
还是两者都是?目前,我的宏仅检查a
是null
,因为它对我来说似乎更自然,但在某些情况下这可能不是理想的行为。此外,在我的宏中检测隐式转换是一种小问题,如this question中所述。宏:
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"
答案 1 :(得分: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())
答案 2 :(得分: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)