Java布尔运算符优先级与三目运算符的比较

3
我发现了一个与Java布尔运算符优先级有关的不太理解的好情况。我已经查阅了Oracle官方文档这里,证实了&&和||比三元运算符?:具有更高的优先级。
现在我的代码中有一行奇怪的代码,类似于这样:
if (a.getItem() != null && a.getItem().getOtherItem() != null?true:a.getItem().getOtherItem().getSomevalue())
{
......
}

我得到的是一个漂亮的java.lang.NullPointerException,因为a.getItem()为空,而在a.getItem().getOtherItem()处出现了该异常。我该如何解决它?将其用括号括起来。
if (a.getItem() != null && (a.getItem().getOtherItem() != null?true:a.getItem().getOtherItem().getSomevalue()))
{
......
}

所以我的问题是,为什么如果我遵循先前链接的官方文档,并且 && 比 ?: 具有优先级,并且 && 是短路评估的,我会得到 NullPointerException(在某些 问题 中也回答了这个问题)。

Elvis 操作符是一些不同的东西 -- 空合并操作符 -- 它在 Java 中不存在。 - Patrick Collins
是的,出于这个原因,我使用 -- a.getItem().getOtherItem() != null -- 而不是 -- a.getItem().getOtherItem()? --,我知道Java中的Elvis运算符是布尔类型的。但我猜那会影响运算符优先级。 - kszosze
“:”用于创建“if-else”语句,布尔型变量running = value1.equals("run")? true : false; - Ker p pag
1
@kszosze 我认为你误解了:Java 中没有所谓的 Elvis 操作符,这不是 Java 的一个特性。 - Patrick Collins
那么 codeif-elsecode 比 && 运算符更具优先级?在这种情况下,为什么当我将其封装在 code( )code 中时可以正常工作。 - kszosze
@PatrickCollins 好的,我用了不好的名字。没有 Elvis 操作符,那么我必须说三目运算符。 - kszosze
3个回答

6
似乎你对“优先级更高”这个概念有些困惑。我们通过一个简单的例子来解释一下:
操作符“*”比操作符“+”具有更高的优先级。这意味着表达式“a*b+c”被看作是“(a*b)+c”。 对于“&&”运算符和三元运算符也是同样的规则:
“&&”运算符比“?:”运算符具有更高的优先级, 这意味着表达式“a&&b?c:d”被看作是“(a&&b)?c:d”。
因此,操作符的优先级按照你的例子所示正常工作。它就是你要求的那样做到了。
if (a.getItem() != null && a.getItem().getOtherItem() != null?
                                       true:a.getItem().getOtherItem().getSomevalue())

如果 a.getItem() 不为 null 且 a.getItem().getOtherItem() 不为 null,则计算结果为 true,否则计算结果为 a.getItem().getOtherItem().getSomevalue()。因此,当其中一个值为 null 时,代码将尝试计算第三个术语,这将导致 NullPointerException
不清楚您实际想要实现什么。在您的第二个示例中,您说:
if (a.getItem() != null && (a.getItem().getOtherItem() != null?
                            true: a.getItem().getOtherItem().getSomevalue()))

a.getItem()null时,您希望将其解释为false,但在括号中的术语中,您请求将a.getItem().getOtherItem()不是null的情况解释为true,而a.getItem().getOtherItem()null的情况应该调用getSomevalue()引用,您已经证明了它是null

您最有可能想要做的是在所有值都不为null的情况下评估a.getItem().getOtherItem().getSomevalue()如果所有值都不为null

if (a.getItem() != null && a.getItem().getOtherItem() != null?
                                     a.getItem().getOtherItem().getSomevalue(): false)

请注意,您可以完全不使用三元运算符来表达相同的意思。等价的语句如下:

请注意,您可以完全不使用三元运算符来表达相同的意思。等价的语句如下:

if (a.getItem() != null && a.getItem().getOtherItem() != null
                        && a.getItem().getOtherItem().getSomevalue())

在这种情况下,回退值应该是true,就像这样
if (a.getItem() != null && a.getItem().getOtherItem() != null?
                                     a.getItem().getOtherItem().getSomevalue(): true)

同样的意思也可以表达为

if (a.getItem() == null || a.getItem().getOtherItem() == null
                        || a.getItem().getOtherItem().getSomevalue())

无论何时在复合布尔表达式中看到truefalse,都可以确定存在问题。

这个示例只是一个表达问题的简单方式。我知道我可以使用三元运算符重新编写它。只是偶然发现进行一些寻找错误的测试时。主要问题是如果 && 比 ?: 的优先级高且 && 是短路运算符,为什么在左侧分支为 false 的情况下三元运算符被评估。 - kszosze
@kszosze:请读一下我的第一个段落。你完全误解了优先级的含义。它意味着a && b? c: d等同于(a && b)? c: d。而短路运算意味着可以跳过对b的评估,没有其他意思。你的期望是相反的,如果? :的优先级更高,那就是这种情况。 - Holger
Holger,好的,我明白了。谢谢。 - kszosze

0

? 的一般用途是在简单的赋值分支中替换 if,例如:

int a;
if(isOdd(b) || isPrime(b))
{
  a = b;
}
else
{
  a = -b;
}

简化为

int a = isOdd(b) || isPrime(b) ? b : -b;

对于这种用例,&&||优先于?是有意义的。

只有当?返回boolean时,才会产生混淆,例如在if语句中使用它的方式,在我看来非常罕见。


我知道我制作的示例方式很奇怪。那行只是一个示例,用于进行一些测试以捕获错误并查看此内容。正如您所说,code && code优先于code?:code,而且code && code是短路评估的,因此如果不使用曲线括号,我不明白为什么会出现NullPointerException。 - kszosze

0

我会说,在你的if语句中

if (a.getItem() != null && a.getItem().getOtherItem() != null?true:a.getItem().getOtherItem().getSomevalue())

必须首先评估以下最内部的部分

null?true:a.getItem().getOtherItem().getSomevalue()

这样就可以将下面的部分组合在一起

if (a.getItem() != nulla.getItem().getOtherItem() !=<result_from_most_inner_part>)

无论如何,这个if语句都很丑陋。让代码更易读,编译器会尽其所能的 :P

为什么当优先级指定左分支必须先执行,而且code&&code是一个短路运算符时,最内部的部分必须首先进行评估。我感到有些不合理。 - kszosze

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接