groovy null安全操作符,识别什么是null?

时间:2014-02-21 03:34:09

标签: groovy

Groovy中的null安全操作符非常适合减少代码并使事情更具可读性。我们可以这样做:

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)

但没有什么是免费的;使用空/安全导航,如果claim为空,则不会立即明显 导致它:custNopolicyNoclaimNo可能无效。

我们可以回去开始检查什么是空的,但这会适得其反,实际上,由于中间对象不存储在变量中,所以甚至不可能。

所以问题是:使用空/安全导航链接方法调用时是否可以识别出什么是空的?

更新

我使用dynamic method invocation对此进行了另一次尝试。它需要初始化目标(通常是dao)来初始化对象(在这种情况下是客户),以及包含方法名称作为字符串的映射(参数作为值)。使用迭代器,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
  }

用法

customer模拟initTarget 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

我喜欢这种方法,因为它小巧,易读且易于使用;你怎么看?

2 个答案:

答案 0 :(得分:3)

我认为没有明确的方法可以这样做。 我可能错了,稍后会查看来源,但安全导航是if语句的语法糖。

作为一个hack,你可以使用interceptor包装代码,在里面跟踪最后一个方法调用,然后使用该信息来提供错误消息。

它不会便宜,并且会花费你一些代码来实现拦截和运行时的一些性能。但你可以实现像

这样的东西
    mayFail("getCusomer", "getPolicy", "getClaim") {
         getCustomer(custNo)?.getPolicy(policyNo)?.getClaim(claimNo)
    } == "getPolicy" // failed on second step

编辑 @tim_yates 证明,?.是一个语法糖,如果构造落后。感谢 Vorg van Geir 获取链接,我在这里copied给出答案。他说,它已经过时了,看起来他是对的。我已经设法使ProxyMetaClass工作(在Groovy 2.0.6中),所以给定的方式并没有完全打破。现在我需要指定要拦截的确切类,我找不到一种方法来捕获继承的方法调用。(简单地拦截java.lang.Object

def logInterceptor = new TracingInterceptor()
logInterceptor.writer = new StringWriter()

def intProxy = ProxyMetaClass.getInstance(Integer)
def stringProxy = ProxyMetaClass.getInstance(String)
intProxy.setInterceptor(logInterceptor)
stringProxy.setInterceptor(logInterceptor)
intProxy.use {
    stringProxy.use {
       println(("Hello" + "world").size().hashCode())
}   }

println(logInterceptor.writer.toString())

所有这些都可能包含在一些实用程序代码中,但我非常怀疑这一点是否必要。性能开销很糟糕,一些样板代码仍然存在。

这场比赛不值一提。

答案 1 :(得分:3)

归因和断言怎么样?

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' )
}