Java中的逻辑运算顺序令人困惑

Posted

技术标签:

【中文标题】Java中的逻辑运算顺序令人困惑【英文标题】:Order of Operations with Logic in Java Confused 【发布时间】:2019-08-03 15:21:36 【问题描述】:

我有一个测验中的操作顺序问题,但解释并不完全有帮助。代码如下:

package com.udayan.oca;

public class Test 
     public static void main(String [] args) 
         int a = 2;
         boolean res = false;
         res = a++ == 2 || --a == 2 && --a == 2;
         System.out.println(a);
     

它说它打印了 3 ,因为我测试了它,但我不明白如何。这是他们的解释:

a++ == 2 || --a == 2 && --a == 2;

[给定表达式]。 (a++) == 2 || --a == 2 && --a == 2;

[后缀的优先级高于其他运算符]。

(a++) == 2 || (--a) == 2 && (--a) == 2;

[后缀之后,前缀优先]。

((a++) == 2) || ((--a) == 2) && ((--a) == 2);

[== 优先于 && 和 ||]。

((a++) == 2) || (((--a) == 2) && ((--a) == 2));

[&& 优先于 ||]。

让我们开始解决它:((a++) == 2) || (((--a) == 2) && ((--a) == 2));

[a=2, res=false]。

(2 == 2) || (((--a) == 2) && ((--a) == 2));

[a=3, res=false]。 true || (((--a) == 2) && ((--a) == 2));

[a=3, res=false]。

||是一个短路运算符,因此不需要计算右边的表达式。

res 为真,a 为 3。

是的,顺便说一下,我理解短路,所以不需要解释。

不过这是我的想法:

res = a++ == 2 || --a == 2 && --a == 2 ->
(((a++) == 2) || (((--a) == 2) && ((--a) == 2))) [a = 2]

(((a++) == 2) || ((**1** == 2) && ((--a) == 2))) [a = 1]

(((a++) == 2) || (**false** && (**0** == 2))) [a = 1] //short-circuits

(((a++) == 2) || **false**) [a = 1] //short circuits

(**false**) [a = 1]

???? 另一点是答案键说先做a++然后||下一个。 a++ 是的,这是有道理的。但我认为 && 在 || 之前。

【问题讨论】:

【参考方案1】:

来自Java Language Specification,

The conditional-or operator || operator is like | (§15.22.2), but evaluates its right-hand operand only if the value of its left-hand operand is false.

所以,这比你想象的要简单。 res = a++ == 2 || --a == 2 && --a == 2; 的评估方式如下:

res = ((a++ == 2) || ((--a == 2) && (--a == 2)));

a++ == 2?后增量意味着 a 被读取为 2。然后评估该表达式。 2 == 2,这是真的。短路意味着表达式的其余部分从不计算。

所以,上面的代码基本上都是res = a++ == 2;

我做了一个简单的程序来测试这个:

public class TestSOCode 
    public static void main(String [] args) 
        test1();
    

    private static void test1()
        int a = 2;
        boolean res = false;
        //res = a++ == 2 || --a == 2 && --a == 2;
        res = expression(a++, "One") || expression(--a, "Two") && expression(--a, "Three");
        System.out.println(res +" "+ a);
    

    private static boolean expression(int i, String s)
        System.out.println(s+ " called with "+ i);
        return i == 2;
    

这给出了结果

One called with 2
true 3

更新:经过一些讨论和研究,我认为在涉及逻辑运算符时,对优先级和执行顺序之间的区别存在误解。

res = a++ == 2 || --a == 2 && --a == 2;

上述语句的优先级是在评估它之前计算出来的。我不会讨论其他优先规则,因为它会使这个答案复杂化,所以我会简化它:

res = x || y && z;

&& 优先,因此表达式按如下方式组合在一起:

res = x || (y && z);

正如我们所见,&& 具有优先权,因此它左右的表达式被组合在一起,然后 || 被评估。左边的表达式是x,右边的表达式是(y && z)(我想我们都在想如果&&优先,它会像(a || b) && c,所以它会首先被评估,但那是不是它是如何工作的)。如果我们想看看事实是否如此,我们可以像这样修改上面的代码:

 res = expression(a = 8, "One") || expression(a = 16, "Two") && expression(a = 32, "Three");

这等效于false || (false && false),但编译器不会对编译时常量产生任何干扰。这样做的结果是:

One called with 8
Two called with 16
false 16

首先,评估||,然后评估&& 的左侧。这将返回 false,而false && ? 将始终为 false,因此不计算第三个表达式。但是没有违反优先规则。我希望这已经消除了任何困惑。如果没有,我很乐意在聊天中继续讨论并更新我的答案。因为我们从原始代码中知道,如果第一个表达式为真,|| 返回真和短路,我们可以说a || b && c 没有组合成(a || b) && c

【讨论】:

但是如果 || 会短路有更高的优先级...不是 && 有更高的优先级吗?所以在它之前||短路,需要处理&&? 没有。它首先评估|| 的左侧。我将在我的答案中添加一些测试代码 据此我知道它首先评估左侧,但根据我所看到的一切,&& 具有更高的优先级,这意味着它需要先评估 &&... 所以,我认为编译器可能正在优化代码,因为它可以看到 a 在编译时为 2。我尝试使用 Math.random() 将 a 设置为编译器在编译时看不到的东西,但如果该随机数为 2,则答案是相同的。我唯一的结论是它首先读取||,尽管它们的优先级明显更高。 我试过 boolean bar = foo(true, 1) ||富(假,2)&&富(真,3);其中 foo 是打印第二个参数并返回第一个参数的静态布尔方法。它所做的只是打印 1。这意味着它在做真实的 || foo(false, 2) && foo(true, 3) 和短路而无需打扰 && 好像 ||和 && 只是从左到右。富(真,3)&&富(真,1)|| foo(false, 2) -> 3 1. 所以无论是哪种顺序,它似乎都是从左到右的。【参考方案2】:
res = a++ == 2 || --a == 2 && --a == 2 (res is true)

1. a++ (post-increment, no) -> a = a + 1 -> it's still 2 -> when true -> it becomes 3
2. --a (pre-increment, right to left) -> a - 1 = a -> 1
3. --a (pre-increment, right to left) -> a - 1 = a -> 0 (its because of logical and, it never execute this part) 
4. == (equality, left to right) -> 2 == 2 || 1 == 2 && 0 == 2 -> true || false && false
5. && (logical and, left to right) -> false -> no more steps 
6. || (logical or, left to right) -> true -> go to 1.

// so take 3
// moral of the story is always use paranthesis
// op is correct for short-circuit 

【讨论】:

但是括号会导致我这样做......并得到0? 你是怎么得到 0 的?这是不可能的 第 3 步结果 a = 0。a 在 4 5 和 6 中不再更改 好的,我想我知道你为什么仍然困惑。优先级有点像分组。因此,它将逐步执行。你为什么不试着颠倒等式并像我一样按照步骤告诉我为什么它得到 2. res = --a == 2 && --a == 2 ||一个++ == 2; (记住要遵循算子级别和关联性,然后你就会明白短路是什么) 1. a++ -> --a == 2 && --a == 2 || 2 == 2 其中 a = 3 //换行符// 2. --a -> 1 == 2 && --a == 2 || 2 == 2 其中 a = 1 //换行符// 3. 1 == 2 && 0 == 2 || 2 == 2 where a = 0 //linebreak false && false ||真 -> 假【参考方案3】:

最后当(((a++) == 2) || false) [a = 1] 然后作为 || 完成运算符的优先级低于 ++ 所以这里 a 将变为 3 .. 然后它将打印 a=3 虽然它是一个短路运算符,但它仍然必须执行 ++ 运算符 1st。

【讨论】:

以上是关于Java中的逻辑运算顺序令人困惑的主要内容,如果未能解决你的问题,请参考以下文章

逻辑运算符混合运算 令人恶心的 i++ 与 ++i

JAVA中的关系运算符的优先级是啥意思?还有逻辑运算符的优先级

R语言比较运算符和逻辑运算符顺序

使用双重逻辑非(!!)运算符感到困惑[重复]

Java运算符优先级谁知道?

Java中运算符的先后顺序