(a = = 1 & & a = = 2 & & a = = 3)能在 Java 中计算为真吗?

我们知道它可以在 JavaScript 中实现。

但是有没有可能在 Java 中给出的条件下打印“ Success”消息呢?

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

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

25767 次浏览

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

一个线程不断地将变量 a 从1改为3,另一个线程不断地测试该 a == 1 && a == 2 && a == 3。这种情况经常发生,足以在控制台上连续地打印“ Success”流。

(注意,如果添加 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();
}
}

由于 Erwin BolwidtPhflack的答案都很好,我们已经知道 有可能可以让这段代码求值为真。我想说明的是,当处理一个看起来像问题中提到的情况时,你需要保持高度的注意力,因为有时你看到的可能并不完全是你想的那样。

我试图展示这段代码将 Success!打印到控制台。我知道我作弊了,但我仍然认为这是一个很好的地方来展示它就在这里。

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

我使用了西里尔字母的“ a”,这是一个与拉丁字母“ a”截然不同的字符。可以检查 if 语句 给你中使用的字符。

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

注意,如果你想让这段代码正常工作,字符编码需要改为同时支持两个字符,例如所有的 Unicode 编码(UTF-8,UTF-16(在 BE 或 LE 中) ,UTF-32,甚至 UTF-7) ,或者 Windows-1251,ISO 8859-5,KOI8-R (谢谢 -Thomas Weller(咒语)-指出这一点) :

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

(我希望你以后再也不用处理这类问题了。)

使用来自 聪明的高尔夫球密码答案的概念(和代码) ,Integer值可能会被混淆。

在这种情况下,它可以使浇铸到 Integer上的 int在正常情况下是相等的:

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铸造)那样优雅,但仍然有一些有趣的恶作剧发生。

因为这看起来像是 这个 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 的文本工具可能甚至不会报告任何差异,在搜索时总能找到两者。您甚至可能会遇到这样的问题: 在将源代码复制到其他人时,不同的表示会丢失,可能会试图为“奇怪的行为”寻求帮助,从而使其无法为助手重现。

这个问题@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");
}
}

如果使用以下命令执行,它将输出 没错1 Success:

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

还有另一种方法可以解决这个问题(除了我前面提到的易失性数据竞赛方法之外) ,那就是使用 PowerMock 的强大功能。PowerMock 允许用其他实现替换方法。当这与自动拆箱相结合时,原来的表达式 (a == 1 && a == 2 && 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());
}
}


}

沿着 相似的台词,通过强制浮点数(或双精度数)通过大数除法(或乘法)下溢(或溢出) :

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

受@Erwin 优秀的 回答的启发,我编写了一个类似的示例,但使用的是 Java数据流 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");
}
}
}

P.S. 并行流在底层使用 ForkJoinPool,变量 在多个线程之间共享,没有任何同步,这就是为什么结果是不确定的。