groovy null-safe运算符,标识什么是null?

groovy null-safe运算符,标识什么是null?,groovy,Groovy,Groovy中的空安全运算符对于减少代码和提高可读性非常有用。我们可以这样做: def customer = getCustomer(custNo) if(!customer) throw new Exception("Invalid customer: ${custNo}") def policy = customer.getPolicy(policyNo) if(!policy) throw new Exception("Invalid policy: ${policyNo}"

Groovy中的空安全运算符对于减少代码和提高可读性非常有用。我们可以这样做:

def customer = getCustomer(custNo)
if(!customer)
   throw new Exception("Invalid customer: ${custNo}")

def policy = customer.getPolicy(policyNo)
if(!policy)
   throw new Exception("Invalid policy: ${policyNo}")

def claim = policy.getClaim(claimNo)
if(!claim)
   throw new Exception("Invalid claim: ${claimNo}")
…对这个

def claim = getCustomer(custNo)?.getPolicy(policyNo)?.getClaim(claimNo)
但没有什么是免费的;使用空/安全导航,如果
索赔
为空,则不清楚是什么导致了它:要么
客户号
保单号
,要么
索赔号
可能无效

我们可以返回并开始检查空值,但这会适得其反,实际上,这甚至是不可能的,因为中间对象不存储在变量中

因此,问题是:在使用null/safe导航链接方法调用时,是否可以识别null?

更新 我又试了一次。它需要一个init目标(通常是dao)来初始化对象(在本例中是customer),以及一个包含方法名作为字符串(参数作为值)的映射。使用迭代器,
invokeChain
简单地遍历映射(链);如果链中的任何内容返回null,那么识别导致它的方法就变得微不足道了

  def invokeChain = { initTarget, chain ->
     def obj
     chain.eachWithIndex{ it, idx ->
        //init obj from dao on first iteration only,
        //remaining iterations get from obj itself
        obj = (!idx) ? initTarget."$it.key"(it.value) : obj?."$it.key"(it.value)            

        if(!obj)
           throw new Exception("${it.key}(${it.value}) returned null")           
     }
     obj
  }
用法 为
initTarget
模拟
customer
dao……我已经插入了
null
作为
getClaim()
的返回类型,这应该会引发异常

   def static getCustomer = { custNo ->
      [ getPolicy: { p ->           
            [getClaim:{ c ->
                  null //"Claim #${c}"
               }]
         }]
   }
…使用
invokeChain
,非常简单:

  def claim = invokeChain(this, [getCustomer:123, getPolicy:456, getClaim:789])
…按预期引发异常:

Exception in thread "main" java.lang.Exception: getClaim(789) returned null

我喜欢这种方法,因为它紧凑、可读、易于使用;你觉得怎么样

我认为没有明确的方法可以做到这一点。 我可能是错的,稍后将检查源代码,但安全导航是if语句的语法糖

作为一种黑客,您可以用它来包装代码,跟踪最后一个方法调用,然后使用该信息提供错误消息

它不会便宜,并且会花费一些代码来实现拦截,并在运行时获得一些性能。但是你可以做到

    mayFail("getCusomer", "getPolicy", "getClaim") {
         getCustomer(custNo)?.getPolicy(policyNo)?.getClaim(claimNo)
    } == "getPolicy" // failed on second step
EDIT:正如@tim_yates所证明的,
?。
是一种语法糖,后面有if结构。感谢Vorg van Geir的链接,我这里有答案。他说,这已经过时了,看起来他是对的。我已经设法使ProxyMetaClass工作(在Groovy 2.0.6中),所以“给定方式”并没有完全中断。现在我需要指定要截取的确切类,但我找不到捕获继承的方法调用的方法。(只截取
java.lang.Object

所有这些地狱可能都包含在一些实用程序代码中,但我高度怀疑这样做的必要性。性能开销将是可怕的,一些样板代码将保留下来


这场比赛不值一根蜡烛。

那么归因和断言呢

def policy = customer?.policy
def claim = policy?.claim
def number = claim?.number

assert customer, "Invalid customer"
assert policy, 'Invalid policy'
assert claim, 'Invalid claim'

更新:

您已经找到了解决方案,但我想提出一个拦截器的想法:

模拟:

def dao = [
  getCustomer : { custNo ->
    [ getPolicy: { p ->           
      [getClaim:{ c ->
          null //"Claim #${c}"
      }]
    }]
  }
]
拦截器:

class MethodCallInterceptor {
  def delegate
  def invokeMethod(String method, args) {
    def result = delegate.invokeMethod(method, args)
    if (result == null) {
      throw new RuntimeException("$method returned null")
    }
    else {
      new MethodCallInterceptor(delegate: result)
    }
  }

  def getProperty(String property ) { 
    delegate[ property ]
  }

  void setProperty(String property, value) {
    delegate[ property ] = value
  }
}
测试:

def interceptedDao = new MethodCallInterceptor(delegate: dao)

try {
  interceptedDao.getCustomer(123).getPolicy(456).getClaim(789)
  assert false
} catch (e) {
  assert e.message.contains( 'getClaim returned null' )
}

或者,如果(customer!=null){policy=customer.getPolicy(policyNo)…Codehaus页面上的代码是针对Groovy 1.5的,大部分不再工作(Groovy中添加了许多功能,但只针对单个版本进行了测试,然后悄悄地被遗忘了)。我重新发布了,但即使是那个也可能过时。@VorgvanGeir感谢您的建设性反馈。我扩展了一个答案,并在那里包含了您的链接。我提供了一个有效的拦截示例,并略微扩展了一个答案。感谢您提供了有趣的调查刺激。是的,该解决方案非常好。它没有拦截?.s我注意到,映射顺序可能会改变方法调用,但由于Groovy默认使用LinkedHashMap,这似乎很好。另外,我尝试玩MOP,只是为了好玩,如果我得到了好的结果,我会把它发布在这里。很抱歉,评论太晚了,我有点忙。这方面的问题s是链中每个元素的
assert
,必须手动添加;我添加了解决此问题的解决方案,请参见上文。我以为您想要解决单个问题,而不是通用解决方案。有趣的解决方案。我正在考虑一个装饰器,但我想结果或多或少会是相同的same@raffian,我建议国际米兰ceptor想法:-)谢谢,威尔,我们的想法是一样的,无论哪种方式都很好,一般来说,这对我很重要,应该在前面提到。
def interceptedDao = new MethodCallInterceptor(delegate: dao)

try {
  interceptedDao.getCustomer(123).getPolicy(456).getClaim(789)
  assert false
} catch (e) {
  assert e.message.contains( 'getClaim returned null' )
}