但是在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");
}
但是这样做我们正在改变实际的变量。还有其他方式吗?
但是在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");
}
但是这样做我们正在改变实际的变量。还有其他方式吗?
如果您将变量a
声明为volatile,使用多个线程很容易实现这一点。
一个线程不断地将变量a从1更改为3,另一个线程不断测试a == 1 && a == 2 && a == 3
。它发生得足够频繁,可以在控制台上持续输出"Success"。
(请注意,如果添加else {System.out.println("Failure");}
子句,您会发现测试失败的次数远远超过成功的次数。)
实际上,在不将a
声明为volatile的情况下,它也可以工作,但在我的MacBook上只能运行21次。没有volatile
,编译器或HotSpot可以缓存a
或将if
语句替换为if (false)
。最有可能的是,HotSpot会在一段时间后启动并将其编译为缓存a
值的汇编指令。 使用volatile
,它将永远输出"Success"。
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
关键字也不能保证它一定会发生。当然,在实践中发生这种情况是相当令人印象深刻的。 - Holgervolatile
是很可能发生的。为了实现volatile
而创建的内存屏障会降低线程速度,并增加它们在短时间内同步操作的可能性。这种情况比我预期的要频繁得多。虽然这非常依赖于时间,但我大约看到0.2%至0.8%的(a == 1 && a == 2 && a == 3)
计算结果返回true
。 - Erwin Bolwidta
不是final或有效final的情况下仍然可以访问它?如果我错了,请纠正我,您的代码似乎与Java 8文档相矛盾。 - dkonayukia
是一个字段,而不是一个局部变量。'final' 和 'effectively final' 规则只适用于局部变量。 - Erwin Bolwidt利用来自一篇出色的代码高尔夫答案的概念(和代码),可以玩弄 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
转型),但仍然有一些有趣的骗局发生。
Integer
的滥用。虽然转换很遗憾,但它仍然很酷。 - Michaela
设置为1/2/3时,它满足a == 1
,但反过来则不一定成立。 - phflackJava
中回答了这个问题。我找到的最好的版本是使用 a.equals(1) && a.equals(2) && a.equals(3)
,这将强制 1
、2
和 3
被自动装箱为 Integer
。 - Eric Duminila
设置为具有boolean equals(int i){return true;}
的类,那该怎么办? - phflackInteger a = 1;
,那么很明显 a
真的是一个整数。 - Eric Duminil这个问题中,@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
a
是一个变量,但它可以是任何任意的代码片段。 - kevin正如我们已经知道的,由于Erwin Bolwidt和phflack提供的出色答案,使得这段代码能够评估为true。我想展示的是,在处理类似于问题中呈现的条件时,需要保持高度的注意力,因为有时您看到的可能不完全是您认为的。
这是我尝试显示此代码将Success!
打印到控制台。 我承认我有点作弊,但我仍然认为这是一个好地方来展示它。
无论编写此类代码的目的是什么 - 最好了解如何处理以下情况以及如何检查您是否对所看到的东西感到困惑。
我使用了Cyrillic '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!");
}
}
}
我希望你将来永远不必面对那种问题。
а
和a
的任何字符编码都可以,只要你告诉你的编辑器它使用的是哪种编码(可能还要告诉编译器)。所有Unicode编码都可以工作(UTF-8,UTF-16(BE或LE),UTF-32,甚至UTF-7),例如Windows-1251,ISO 8859-5,KOI8-R也可以。 - Paŭlo Ebermann除了我之前发布的易失性数据竞争方法,还有另一种方法可以解决这个问题,那就是使用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());
}
}
}
access$nnn
这样的合成方法来读取内部/外部类的私有字段?这将允许一些其他有趣的变体(甚至适用于int变量)... - Holger(Integer)2
)将int装箱。在更深入地研究反射时,看起来使用反射无法进行拆箱,但可能可以使用Instrumentation代替_(或者像这个答案中一样使用PowerMock)_。 - phflackaccess$0
的替换项,并在注册时检查方法的存在。但是替换从未被调用。 - Erwin Bolwidt因为这似乎是此 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");
}
}
}
但请注意,这不是您可以使用Unicode做的最糟糕的事情。使用作为有效标识符部分的空格或控制字符,或者使用外观相同的不同字母,仍会创建不同的标识符,并且可以在进行文本搜索时被发现。
但这个程序
public class Q48383521 {
public static void main(String[] args) {
int ä = 1;
int ä = 2;
if(ä == 1 && ä == 2) {
System.out.println("success");
}
}
}
从Unicode的角度来看,ä
使用了两个相同的标识符,只是用不同的方式进行编码,其中一种是 U+00E4
,另一种是 U+0061 U+0308
。
因此,取决于你使用的工具,它们可能不仅外观相同,而且启用Unicode的文本工具甚至可能不报告任何差异,始终在搜索时发现两者。你甚至可能会遇到这样的问题:在将源代码复制给其他人以获取帮助解决“奇怪行为”时,不同的表示会丢失,从而使帮助者无法再现。
int ᅠ2 = 3;
这是有意为之的吗?因为我看到周围都是非常奇怪的代码。 - Ravijust-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
,变量 a 在多个线程之间共享且没有任何同步,因此结果是不确定的。
类似的,通过将浮点数(或双精度数)强制进行除法(或乘法)操作,并使其下溢(或上溢):
点击此处了解更多。
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");
}
&&
是逻辑与运算符,意味着a
同时应该具有值 1、2 和 3,这在逻辑上是不可能的。答案是否定的,不可能实现。你想编写一个if
语句来检查a
是否具有值 1、2 或 3 中的一个吗? - Boris Pavlovićif(a==1 && a==2 && a==3)
不一定在同一时间被计算。这可以用来使代码工作而无需使用 Unicode 技巧。 - Erwin Bolwidt