XTend 空安全抛出 NullPointerException

XTend null safe throws NullPointerException

我正在将我的模板代码移植到 XTend。在某些时候,我在测试用例中有这种类型的条件处理:

@Test
def xtendIfTest() {
    val obj = new FD
    if (true && obj?.property?.isNotNull) {
        return
    }
    fail("Not passed")
}

def boolean isNotNull(Object o) {
    return o != null
}
class FD {
 @Accessors
 String property
}

这按预期工作,因为 属性 为空并且测试将失败并显示 "Not passed" 消息。但是将 return 类型的 isNotNull 方法简单更改为 Boolean (wrapper):

def Boolean isNotNull(Object o) {
    return o != null
}

失败并出现 NullPointerException。为此检查生成的 java 代码,我可以看到 XTend 使用了一个中间布尔对象表达式,这就是 NPE 的原因。我是否遗漏了 XTend 空安全运算符 (?.) 的要点,或者我不能在运算符之后使用这样的方法?

谢谢。

接线员行为正常。抛出异常是因为在if表达式中使用了布尔值,需要自动拆箱。

如果您尝试以下操作:

@Test
def xtendIfTest() {
    val Boolean obj = null
    if (obj) {
        return
    }
    fail("Not passed")
}

你也会运行进入NullPointerException。

这符合 Java 语言规范 (https://docs.oracle.com/javase/specs/jls/se7/html/jls-5.html#jls-5.1.8) - 当需要自动拆箱时,这会产生 NullPointerException:

@Test
public void test() {
    Boolean value = null;
    if (value) { // warning: Null pointer access: This expression of type Boolean is null but requires auto-unboxing
        // dead code
    }
}

希望对您有所帮助。

简答:将第二个空安全调用更改为常规调用。

即变化

obj?.property?.isNotNull

对此:

obj?.property.isNotNull

长答案:

docs 是这样描述空安全运算符的:

In many situations it is ok for an expression to return null if a receiver was null

这意味着您示例中的第二个调用,如果调用的左侧是 nullproperty?. 甚至不会调用 isNotNull。相反,它将 return null。所以条件 "effectively" 的计算结果为:

if (true && null) {  // causes NPE when java tries to unbox the Boolean 

(顺便说一句 - true 在这种情况下是多余的,但我保留它以防万一你有另一个条件要检查 - 我假设你只是将它简化为 true 对于这个例子。)

如果您进行我建议的更改,将评估 obj?.property,然后将结果传递给 isNotNull,评估为:

if (true && isNotNull(null)) {

哪个 return 是正确的 Boolean 对象,将按预期自动拆箱。

注意事项

isNotNull 的第一种形式中,即 returning 原语 boolean,你实际上应该得到像 "Null-safe call of primitive-valued feature isNotNull, default value false will be used".

这样的警告

这是因为您扩展了空安全调用的意图,即 return null 如果运算符的左侧是 [=],则不调用右侧方法16=]。但是如果你的 isNotNull return 是一个原始的 boolean,整个表达式显然不能计算为 null,所以 Xtend 使用默认值,即 false对于布尔值。

为了以不同的方式强调这个问题 - 它的计算结果为 false 而没有 调用 isNotNull - 这意味着即使你使用了一个方法 isNull 在运算符之后,它会 still return false!

文档还提到了这种行为(尽管是笼统的说法):

For primitive types the default value is returned (e.g. 0 for int). This may not be what you want in some cases, so a warning will be raised by default

因此,我建议始终在空安全调用的右侧使用非原始 return 值。但是,如果您要按照我的建议将 isNotNull 转换为常规调用,则此规则不适用,并且 return 类型都可以。