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


176

我们知道它可以在JavaScript中使用

但是是否可以在下面的Java条件下打印“成功”消息?

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

有人建议:

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

但是通过这样做,我们正在更改实际变量。还有其他办法吗?


5
&&是逻辑and运算符,这意味着a应该同时具有值​​1、2和3,这在逻辑上是不可能的。答案是否定的,不可能。您是否要编写一条if语句来检查是否a具有值1、2或3中的一个?
鲍里斯·帕夫洛维奇(BorisPavlović)

16
所有人注意:这个问题可能来自相同的Javascript问题:stackoverflow.com/questions/48270127/…,在这种情况下,答案是yes
nos '18

28
@ArthurAttout不是重复的,这个问题是针对Javascript,而不是Java。

13
在变量名中使用空格的代码也可以在Java中使用。但是,当然,这与使用基本上相同_,只是您看不到它。
tobias_k

8
@Seelenvirtuose是的,但if(a==1 && a==2 && a==3)不一定要同时进行评估。这样就可以完成这项工作,而不必求助于Unicode技巧。
Erwin Bolwidt

Answers:


324

是的,如果您将变量声明a为volatile ,那么使用多个线程即可轻松实现。

一个线程不断将变量a从1更改为3,而另一个线程不断对其进行测试a == 1 && a == 2 && a == 3。它经常发生,足以在控制台上连续打印“成功”流。

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

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

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();
    }
}

83
这是一个极好的例子!我想下一次与初级开发人员讨论潜在的危险并发问题时,我会偷走它:)
Alex Pruss

6
没有这种情况的可能性很小volatile,但原则上,即使没有该volatile关键字也可能发生这种情况,并且它可以任意次数发生,而即使使用,也没有任何保证volatile。但是,当然,在实践中进行确实令人印象深刻……
Holger

5
@AlexPruss我只对所有开发人员都做了,而不仅仅是我公司的初级人员(我知道这是可能的),答案失败的成功率高达87%
Eugene

3
@Holger是的,两者都不保证。在典型的多全核或多cpu架构上,两个线程都分配给一个单独的内核,尽管很有可能会与发生volatile。为实现volatile线程而创建的内存屏障降低了线程速度,并使其更有可能在短时间内同步运行。它发生的次数比我预期的要多得多。它对时间非常敏感,但是我大致看到(a == 1 && a == 2 && a == 3)return 的评估介于0.2%和0.8%之间true
Erwin Bolwidt

4
这是唯一使目标代码返回true的唯一答案,而不仅仅是对它们进行细微的修改。+1
亚历杭德罗(Alejandro)'18年

85

使用出色的代码高尔夫球答案中的概念(和代码),Integer就可以弄乱值。

在这种情况下,通常情况下,ints 可以使s Integer等于s:

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");
    }
}

不幸的是,它不如Erwin Bolwidt的多线程答案 那么优雅(因为此Integer答案需要强制转换),但是仍然有一些有趣的恶作剧。


很有意思。我在玩弄虐待Integer。对演员表感到羞耻,但仍然很酷。
迈克尔

@Michael我不太清楚如何摆脱这种偏见。 a设置为1/2/3即可满足要求a == 1,但不会
反其道而行

4
我几天前在JS / Ruby / Python和中回答了这个问题Java。我能找到的最难看的版本是使用a.equals(1) && a.equals(2) && a.equals(3),它强制123Integers的形式自动装箱。
埃里克·杜米尼尔

@EricDuminil可能会带走一些乐趣,因为如果您a成为的班级boolean equals(int i){return true;}会怎么样?
phflack

1
正如我所说,这是最难看的,但仍然不是最佳选择。在Integer a = 1;上线之前,仍然很明显a确实是Integer。
埃里克·杜米尼尔

52

这个问题上 @ aioobe建议(并建议)对Java类使用C预处理器。

尽管它非常狡猾,但这是我的解决方案:

#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

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

6
这种感觉就像在实际编译代码之前先通过查找和替换来运行代码,但我可以肯定地看到有人将这样的事情作为其工作流程的一部分
phflack

1
@phflack我喜欢它,可以说这个问题是关于显示在阅读代码时进行假设的危险。容易假设a是变量,但可以是带有预处理器的语言中的任意代码。
凯文'18

2
不是Java。这是“通过C预处理程序后的Java”语言。这个答案使用了类似的漏洞。(注:卑鄙题外话了代码高尔夫现在)
user202729

40

我们已经知道,由于Erwin Bolwidtphflack的出色回答,可以使此代码的评估结果为true ,我想表明在处理看起来像这样的条件时,您需要保持高度关注在问题中,有时您所看到的可能与您所认为的不完全相同。

这是我的尝试,以显示此代码可打印Success!到控制台。我知道我有点作弊,但是我仍然认为这是在此处展示它的好地方。

不管编写这样的代码的目的是什么-更好地了解如何处理以下情况以及如何检查您认为自己所看到的内容是否正确。

我使用西里尔字母“ a”,这与拉丁字母“ a”不同。您可以在此处检查if语句中使用的字符

之所以可行,是因为变量名称取自不同的字母。它们是不同的标识符,创建两个不同的变量,每个变量具有不同的值。

请注意,如果您希望此代码正常工作,则需要将字符编码更改为支持两种字符的编码,例如所有Unicode编码(UTF-8,UTF-16(在BE或LE中),UTF-32甚至UTF-7) ),或Windows-1251,ISO 8859-5,KOI8-R(感谢您-Thomas WellerPaŭloEbermann-指出):

public class A {
    public static void main(String[] args) {
        int а = 0;
        int a = 1;
        if == 0 && a == 1) {
            System.out.println("Success!");
        }
    }
}

(我希望您以后再也不必处理此类问题了。)


@Michael这是什么意思?我已经在手机上编写了它,即使切换到桌面视图,在这里看起来也不错。如果它可以使我的答案更好,请随时对其进行编辑,因为我现在觉得有点难。
普热Moskal

@Michael非常感谢。我以为我在我的回答最后承认是足够的:)
普热Moskal

@mohsenmadi呀,我不能在这里检查它的手机,但我敢肯定,编辑之前,最后一句是关于在我的例子使用不同的语言:P
普热Moskal

为什么█== 0与a == 1类似?
Thomas Weller

1
更加挑剔:您不必一定要使用UTF-8(尽管无论如何还是建议这样做),只要它告诉编辑器使用的是哪种编码(以及可能的编译器),任何兼有а并能a工作的字符编码。所有Unicode编码(UTF-8,UTF-16(在BE或LE中),UTF-32甚至UTF-7)都可以使用,例如Windows-1251,ISO 8859-5,KOI8-R。
圣保罗Ebermann

27

使用PowerMock的功能,还有另一种方法可以解决此问题(除了我之前发布的易失性数据跟踪方法)。PowerMock允许将方法替换为其他实现。当与自动拆箱结合使用时,原始表达式(a == 1 && a == 2 && a == 3)无需修改即可变为真。

@phflack的答案取决于使用Java修改Java中的自动装箱过程 Integer.valueOf(...)调用的。以下方法依赖于通过更改Integer.intValue()呼叫来修改自动拆箱。

下面这种方法的优点是,OP在问题中给出的原始if语句保持不变,我认为这是最优雅的。

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());
        }
    }

}

Powermock可以代替合成方法,例如access$nnn用于读取private内部/外部类字段的方法吗?这将允许其他一些有趣的变体(甚至可以使用int变量)…
Holger

@Holger有趣的主意,我明白你的意思了。它尚不工作-不清楚是什么阻止了它的工作。
Erwin Bolwidt

确实很好,这就是我要尝试的方法,但是我发现从不可变的类更改静态方法在不操纵字节码的情况下将非常困难。似乎我错了,尽管从未听说过PowerMock,但我想知道它是如何做到的。附带说明,phflack的答案不依赖于自动装箱:他更改了缓存的Integer的地址(因此==实际上是在比较Integer对象的地址而不是值)。
Asoub

2
@Asoub转换((Integer)2)将int装箱。 深入研究反射,似乎无法使用反射拆箱来完成此操作,但可以通过代替Instrumentation (或使用PowerMock,如本回答所示)来
做到

@Holger它可能不会拦截合成方法,尽管它允许我注册替代access$0方法,并在注册时检查方法的存在。但是永远不会调用替换。
Erwin Bolwidt

17

由于这似乎是该JavaScript问题的后续内容,因此值得注意的是,该技巧和类似的用法也适用于Java:

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");
        }
    }
}

在Ideone上


但是请注意,这并不是使用Unicode可以做的最坏的事情。使用空格或控制字符(它们是有效的标识符部分)或使用看起来相同的不同字母仍会创建不同的标识符,并且可以将其识别出来,例如在进行文本搜索时。

但是这个程序

public class Q48383521 {
    public static void main(String[] args) {
        int ä = 1;
        int ä = 2;
        if(ä == 1 && ä == 2) {
            System.out.println("success");
        }
    }
}

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

在Ideone上

因此,根据您使用的工具的不同,它们可能不仅看起来相同,而且启用Unicode的文本工具甚至可能不会报告任何差异,在搜索时总是会找到两者。您甚至可能会遇到这样的问题,即在将源代码复制到其他人时,不同的表示形式会丢失,可能是试图获得“怪异行为”的帮助,从而使助手无法重现。


3
嗯,这个答案对 Unicode的滥用还不够好吗?
迈克尔

2
int ᅠ2 = 3;是故意的吗?因为,我看到很奇怪的代码各地
拉维

1
@Michael并非完全正确,因为该答案是关于使用两个不同的字母(IDE在搜索时会考虑不同的字母a),而这是关于空格的,而且,这也归功于相关JavaScript问题上更老的答案。请注意,我的评论甚至比Java问题还要早(几天)……
Holger

2
我看不出区别。这两个答案都提供了一种解决方案,其中if语句中的三个标识符在视觉上相似,但由于使用了不寻常的unicode字符而在技术上有所区别。不管是空白还是西里尔字母都是无关紧要的。
迈克尔

2
@Michael,您可能会这样看,这取决于您。如前所述,我想指出的是,五天前针对JavaScript的答案也适用于Java,只是考虑了问题的历史。是的,这对于另一个不涉及链接的JavaScript问题的答案有些多余。无论如何,与此同时,我更新了答案,添加了一个案例,该案例不是关于视觉上相似的字符,而是关于编码相同字符的不同方式,甚至不是“异常的Unicode字符”。这是我每天都在使用的角色…
Holger

4

受@Erwin出色答案的启发,我写了一个类似的示例,但是使用Java Stream API

有趣的是,我的解决方案有效,但是在极少数情况下(因为   just-in-time编译器会优化此类代码)。

诀窍是JIT使用以下VM选项禁用所有优化:

-Djava.compiler=NONE

在这种情况下,成功案例的数量将大大增加。这是代码:

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");
        }
    }
}

PS并行流ForkJoinPool在后台使用,变量a在多个线程之间共享而没有任何同步,这就是为什么结果不确定的原因。


1

沿着类似的线,通过由大量迫使浮子(或双)到下溢(或溢出)通过分裂(或乘法):

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");
}

2
@PatrickRoberts-根据Java文档,此特定行为是浮点下溢。另请参阅这个这个这个
阿洛克
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.