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

Posted

技术标签:

【中文标题】(a==1 && a==2 && a==3) 在Java中可以评估为真吗?【英文标题】:Can (a==1 && a==2 && a==3) evaluate to true in Java? 【发布时间】:2018-07-01 04:46:05 【问题描述】:

We know it can in 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");

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

【问题讨论】:

&& 是逻辑 and 运算符,这意味着 a 应该同时具有值​​ 1、2 和 3,这在逻辑上是不可能的。答案是否定的,不可能。是否要编写 if 语句来检查 a 是否具有值 1、2 或 3 之一? 请注意:这个问题可能来自 Javascript 的同一个问题:***.com/questions/48270127/…,在这种情况下答案是yes @ArthurAttout 不是重复的,这个问题是针对 Javascript,而不是 Java。 使用whitespace in variable names 的那个也适用于Java。但是当然这和使用_基本一样,只是你看不到。 @Seelenvirtuose 是的,但if(a==1 && a==2 && a==3) 不一定要同时评估。这可以用来完成这项工作,而无需求助于 Unicode 技巧。 【参考方案1】:

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

一个线程不断将变量 a 从 1 更改为 3,另一个线程不断测试 a == 1 && a == 2 && a == 3。经常会在控制台上打印连续的“成功”流。

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

实际上,它也可以在不将a 声明为 volatile 的情况下工作,但在我的 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();
    

【讨论】:

这是一个绝妙的例子!我想下次我和初级开发人员讨论潜在危险的并发问题时我会偷它:) 没有volatile 不太可能发生这种情况,但原则上,即使没有volatile 关键字也可能发生这种情况,并且可能发生任意次数,而另一方面,没有保证它永远不会发生,即使是volatile。但是,当然,将其付诸实践,令人印象深刻…… @AlexPruss 我刚刚对所有开发人员进行了测试,而不仅仅是我公司的初级人员(我知道这是可能的),87% 的答案失败成功 @Holger 没错,两者都不保证。在典型的多全核或多 cpu 架构上,两个线程都被分配到一个单独的内核,尽管它很可能会发生在 volatile 上。为实现volatile 而创建的内存屏障会减慢线程速度,并使它们更有可能在短时间内同步运行。它发生的频率比我预期的要多得多。它对时间非常敏感,但我大致看到(a == 1 &amp;&amp; a == 2 &amp;&amp; a == 3) 的评估中有 0.2% 到 0.8% 会返回 true @dkonayuki a 是一个字段,而不是一个局部变量。 “最终”和“有效最终”规则仅适用于局部变量。【参考方案2】:

使用来自brilliant code golf answer、Integer 值的概念(和代码)可能会被弄乱。

在这种情况下,它可以使 ints 转换为 Integers 在它们通常不相等时相等:

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's multithreaded answer 优雅(因为这需要Integer 演员),但仍然有一些有趣的恶作剧发生。

【讨论】:

非常有趣。我在玩滥用Integer。演员阵容有点遗憾,但它仍然很酷。 @Michael 我不太清楚如何摆脱演员阵容。 a 设置为 1/2/3 将满足 a == 1,但不会反过来 我几天前在 JS/Ruby/Python 和 Java 中回答了这个问题。我能找到的最丑的版本是使用a.equals(1) &amp;&amp; a.equals(2) &amp;&amp; a.equals(3),它强制123 自动装箱为Integers。 @EricDuminil 这可能会带走一些乐趣,因为如果你让a 成为boolean equals(int i)return true; 的类呢? 正如我所说,它是最不丑的,但仍然不是最佳的。前面有Integer a = 1;,还是很清楚a真的是一个整数。【参考方案3】:

在this question@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

【讨论】:

这种感觉就像在实际编译之前通过查找和替换运行代码,但我绝对可以看到有人在他们的工作流程中做类似的事情 @phflack 我喜欢它,可以说这个问题是关于在阅读代码时做出假设的危险。很容易假设 a 是一个变量,但它可以是带有预处理器的语言中的任意代码。 这不是 Java。它是“通过 C 预处理器后的 Java”语言。 this答案使用的类似漏洞。 (注意:underhanded 现在对于 Code Golf 来说是题外话 我相信这就是 Java 没有预处理器的确切原因。【参考方案4】:

我们已经知道有可能让这段代码评估为真,这要归功于 Erwin Bolwidtphflack 的出色回答,我想要表明您在处理与问题中提出的情况相似的情况时需要保持高度的注意力,因为有时您所看到的可能与您想象的不完全一样。

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

无论编写这样的代码的目的是什么 - 最好知道如何处理以下情况以及如何检查您认为您看到的内容是否有误。

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

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

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

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

(我希望您将来永远不必处理此类问题。)

【讨论】:

@Michael 你是什么意思它没有很好地显示?我已经在手机上写了它,在我看来它看起来不错,即使切换到桌面视图也是如此。如果它能让我的答案更好,请随时编辑它,因为我现在觉得有点难。 @Michael 非常感谢。我认为我在回答结束时承认的就足够了:) @mohsenmadi 是的,我不能在手机上查看它,但我很确定在编辑之前,最后一句话是关于在我的示例中使用不同的语言:P 为什么 █==0 应该与 a==1 相似? 更多吹毛求疵:您不一定需要 UTF-8(尽管无论如何都建议这样做),任何同时具有 аa 的字符编码都可以,只要您告诉您的编辑它的编码是什么(也可能是编译器)。所有 Unicode 编码都有效(UTF-8、UTF-16(在 BE 或 LE 中)、UTF-32,甚至 UTF-7),以及例如Windows-1251、ISO 8859-5、KOI8-R。【参考方案5】:

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

@phflack 的答案依赖于修改 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 能否取代合成方法,例如用于读取内部/外部类的 private 字段的 access$nnn 方法?这将允许一些其他有趣的变体(甚至可以使用int 变量)...... @Holger 有趣的想法,我明白你的意思。它还没有工作 - 不清楚是什么阻止它工作。 真的很好,这就是我想要做的,但我发现如果不操纵字节码,从不可变类更改静态方法将非常困难。似乎我错了,虽然从未听说过 PowerMock,但我想知道它是如何做到的。附带说明一下,phflack 的答案不依赖于自动装箱:他更改了缓存 Integer 的地址(因此 == 实际上是在比较 Integer 对象地址而不是值)。 @Asoub 铸造 ((Integer)2) 将 int 框起来。 Looking more into reflection,似乎无法通过使用反射取消装箱来做到这一点,但可以使用 Instrumentation 而不是 (或使用 PowerMock,如本答案) @Holger 它可能不会拦截合成方法,尽管它允许我注册 access$0 的替代品,并在注册时检查方法的存在。但是永远不会调用替换。【参考方案6】:

由于这似乎是 this JavaScript question 的后续,值得注意的是 this trick 和 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");
        
    

On Ideone


但请注意,这并不是使用 Unicode 所能做的最糟糕的事情。使用作为有效标识符部分的空格或控制字符或using different letters that look the same 仍然会创建不同且可以被发现的标识符,例如进行文本搜索时。

但是这个程序

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

On Ideone

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

【讨论】:

呃,this answer 对 unicode 滥用的报道还不够好吗? int ᅠ2 = 3; 是故意的吗?因为,我看到到处都是非常奇怪的代码 @Michael 不完全是,因为该答案是关于使用两个不同的字母(在搜索 a 时 IDE 会认为它们不同),而这是关于空白,嗯,它给予了信任到相关 JavaScript 问题的更早的答案。请注意,my comment there 甚至比 Java 问题还要早(几天)…… 我看不出区别。这两个答案都提供了一个解决方案,其中 if 语句中的三个标识符在视觉上相似但由于使用不寻常的 unicode 字符而在技术上不同。无论是空格还是西里尔字母都无关紧要。 @Michael 你可能会这样看,这取决于你。如前所述,我想说的是,五天前给出的关于 JavaScript 的答案也适用于 Java,只是考虑到问题的历史。是的,这对于另一个不涉及链接的 JavaScript 问题的答案有些多余。无论如何,与此同时,我更新了我的答案以添加一个案例,该案例不是关于视觉上相似的字符,而是编码相同字符的不同方式,它甚至不是“不寻常的 unicode 字符”;这是我每天都在使用的角色……【参考方案7】:

受到@Erwin 优秀的answer 的启发,我写了一个类似的例子,但是使用了Java Stream API

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

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

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

附:并行流在底层使用ForkJoinPool,变量a在多个线程之间共享,没有任何同步,这就是结果不确定的原因。

【讨论】:

【参考方案8】:

沿着similar lines,通过除以(或乘法)一个大数来强制浮点(或双精度)下溢(或上溢):

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

【讨论】:

@PatrickRoberts - 根据Java docs,此特定行为是浮点下溢。另见this、this、this 和this。

以上是关于(a==1 && a==2 && a==3) 在Java中可以评估为真吗?的主要内容,如果未能解决你的问题,请参考以下文章

《编程导论(Java)&#183;2.1.3改写(override)》

ssh使用http代理登录工具

898B. Proper Nutrition#买啤酒问题(枚举&取余)

tableView性能优化

Codeforces Round #414 Div.2

HustOJ - 1012