可以(a == 1&& a == 2&& a == 3)在Java中评估为true?

Can (a==1 && a==2 && a==3) evaluate to true in Java?

我们知道它可以在javascript中使用。

但是在Java下面给出的条件下打印"成功"消息是可能的吗?

1
2
3
if (a==1 && a==2 && a==3) {
    System.out.println("Success");
}

有人建议:

1
2
3
4
5
6
int _a = 1;
int a  = 2;
int a_ = 3;
if (_a == 1 && a == 2 && a_ == 3) {
    System.out.println("Success");
}

但是通过这样做,我们改变了实际的变量。还有别的办法吗?


是的,如果将变量a声明为volatile,那么使用多个线程很容易实现这一点。

一个线程不断地将变量A从1更改为3,另一个线程不断地测试该变量。它经常发生,足以在控制台上打印出连续的"成功"流。

(注意,如果您添加一个else {System.out.println("Failure");}子句,您将看到测试失败的次数远远超过它成功的次数。)

实际上,它也可以在不声明a为易失性的情况下工作,但在我的MacBook上只有21次。在没有volatile的情况下,编译器或热点可以缓存a或用if (false)替换if语句。最有可能的是,Hotspot会在一段时间后启动,并将其编译为汇编指令,以缓存a的值。有了volatile,它将永远保持打印"成功"。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class VolatileRace {
    private volatile int a;

    public void start() {
        new Thread(this::test).start();
        new Thread(this::change).start();
    }

    public void test() {
        while (true) {
            if (a == 1 && a == 2 && a == 3) {
                System.out.println("Success");
            }
        }
    }

    public void change() {
        while (true) {
            for (int i = 1; i < 4; i++) {
                a = i;
            }
        }
    }

    public static void main(String[] args) {
        new VolatileRace().start();
    }
}


使用优秀的代码高尔夫答案中的概念(和代码),Integer值可能会受到干扰。

在这种情况下,它可以使int铸造给Integer是相等的,当它们通常不是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.lang.reflect.Field;

public class Test
{
    public static void main(String[] args) throws Exception
    {
        Class cache = Integer.class.getDeclaredClasses()[0];
        Field c = cache.getDeclaredField("cache");
        c.setAccessible(true);
        Integer[] array = (Integer[]) c.get(cache);
        // array[129] is 1
        array[130] = array[129]; // Set 2 to be 1
        array[131] = array[129]; // Set 3 to be 1

        Integer a = 1;
        if(a == (Integer)1 && a == (Integer)2 && a == (Integer)3)
            System.out.println("Success");
    }
}

不幸的是,这并不像欧文·博洛维特的多线程回答那样优雅(因为这个问题需要Integer的演员阵容),但仍然有一些有趣的恶作剧发生。


在这个问题中,AIOOBE建议(并且反对)使用C预处理器来处理Java类。

虽然这是非常欺骗,但这是我的解决方案:

1
2
3
4
5
6
7
8
9
#define a evil++

public class Main {
    public static void main(String[] args) {
        int evil = 1;
        if (a==1 && a==2 && a==3)
            System.out.println("Success");
    }
}

如果使用以下命令执行,它将只输出一个Success

1
cpp -P src/Main.java Main.java && javac Main.java && java Main


正如我们已经知道的,由于Erwin Bolwidt和Phflack给出了很好的答案,这段代码可能会被认为是正确的,我想说明在处理一个看起来像问题中出现的情况时,你需要保持高度的关注,有时你会看到可能不是你想的那样。

这是我试图证明此代码将Success!打印到控制台。我知道我有点作弊,但我仍然认为这是一个很好的地方来展示它。

不管这样编写代码的目的是什么——最好知道如何处理以下情况,以及如何检查您所看到的内容是否正确。

我用的是西里尔字母"A",它是拉丁字母"A"的一个独特的字符。您可以在这里检查if语句中使用的字符。

这是因为变量的名称来自不同的字母。它们是不同的标识符,创建两个不同的变量,每个变量的值都不同。

请注意,如果希望此代码正常工作,则需要将字符编码更改为同时支持这两个字符的编码,例如所有Unicode编码(UTF-8、UTF-16(Be或Le)、UTF-32、甚至UTF-7)或Windows-1251、ISO 8859-5、Koi8-R(谢谢您-Thomas Weller和PA?Lo Ebermann-指出来):

1
2
3
4
5
6
7
8
9
public class A {
    public static void main(String[] args) {
        int а = 0;
        int a = 1;
        if(а == 0 && a == 1) {
            System.out.println("Success!");
        }
    }
}

(我希望你以后任何时候都不必处理这种问题。)


有另一种方法可以解决这个问题(除了我之前发布的易变数据竞速方法之外),使用powermock的强大功能。PowerMock允许用其他实现替换方法。当与自动拆箱结合时,可以不经修改而使原来的表达式(a == 1 && a == 2 && a == 3)变为真。

@ PHFLAK的答案依赖于修改使用EDOCX1 1调用的Java中的自动装箱过程。下面的方法依赖于通过更改Integer.intValue()调用来修改自动取消装箱。

下面的方法的优点是,使用问题中的op给出的原始if语句是不变的,我认为这是最优雅的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import static org.powermock.api.support.membermodification.MemberMatcher.method;
import static org.powermock.api.support.membermodification.MemberModifier.replace;

import java.util.concurrent.atomic.AtomicInteger;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@PrepareForTest(Integer.class)
@RunWith(PowerMockRunner.class)
public class Ais123 {
    @Before
    public void before() {
        //"value" is just a place to store an incrementing integer
        AtomicInteger value = new AtomicInteger(1);
        replace(method(Integer.class,"intValue"))
            .with((proxy, method, args) -> value.getAndIncrement());
    }

    @Test
    public void test() {
        Integer a = 1;

        if (a == 1 && a == 2 && a == 3) {
            System.out.println("Success");
        } else {
            Assert.fail("(a == 1 && a == 2 && a == 3) != true, a =" + a.intValue());
        }
    }

}


由于这似乎是一个后续的JavaScript问题,值得注意的是,这个伎俩和类似的工作也在Java:

1
2
3
4
5
6
7
8
9
10
public class Q48383521 {
    public static void main(String[] args) {
        int a? = 1;
        int ?2 = 3;
        int a = 3;
        if(a?==1 && a==?2 && a==3) {
            System.out.println("success");
        }
    }
}

论伊迪翁

但是请注意,这不是Unicode所能做的最糟糕的事情。使用作为有效标识符部分的空白字符或控制字符,或者使用外观相同的不同字母,仍然会创建不同的标识符,并且可以被发现,例如在进行文本搜索时。

但是这个节目

1
2
3
4
5
6
7
8
9
public class Q48383521 {
    public static void main(String[] args) {
        int a? = 1;
        int ? = 2;
        if(a? == 1 && ? == 2) {
            System.out.println("success");
        }
    }
}

使用两个相同的标识符,至少从Unicode的角度来看是相同的。他们只是使用不同的方法来编码同一个字符?,使用U+00E4U+0061 U+0308

论伊迪翁

因此,根据您使用的工具的不同,它们可能不仅看起来相同,启用Unicode的文本工具甚至可能不会报告任何差异,在搜索时总是同时查找这两者。您甚至可能会遇到这样的问题:当将源代码复制给其他人时,不同的表示会丢失,可能会试图为"奇怪的行为"寻求帮助,从而使帮助者无法复制源代码。


受@欧文优秀答案的启发,我编写了一个类似的例子,但是使用Java流API。

有趣的是,我的解决方案是可行的,但在极少数情况下(因为just-in-time编译器优化了这样的代码)。

诀窍是使用以下VM选项禁用任何JIT优化:

1
-Djava.compiler=NONE

在这种情况下,成功案例的数量显著增加。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Race {
    private static int a;

    public static void main(String[] args) {
        IntStream.range(0, 100_000).parallel().forEach(i -> {
            a = 1;
            a = 2;
            a = 3;
            testValue();
        });
    }

    private static void testValue() {
        if (a == 1 && a == 2 && a == 3) {
            System.out.println("Success");
        }
    }
}

P.S.并行流在引擎盖下使用ForkJoinPool,变量A在多个线程之间共享,而不进行任何同步,这就是为什么结果是不确定的。


沿着类似的线,通过用一个大数除(或乘)来强制一个浮点(或双)下溢(或溢出):

1
2
3
4
5
6
int a = 1;
if (a / Float.POSITIVE_INFINITY == 1 / Float.POSITIVE_INFINITY
        && a / Float.POSITIVE_INFINITY == 2 / Float.POSITIVE_INFINITY
        && a / Float.POSITIVE_INFINITY == 3 / Float.POSITIVE_INFINITY) {
    System.out.println("Success");
}