Может ли (a == 1 && a == 2 && a == 3) иметь значение true в Java?

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?
Борис Павлович
16
Примечание для всех: этот вопрос может быть из того же вопроса для Javascript: stackoverflow.com/questions/48270127/… , в этом случае ответyes
nos
29
@ArthurAttout Не дубликат, этот вопрос касается Javascript, а не Java.
13
Тот, который использует пробелы в именах переменных, работает и в Java. Но, конечно, это то же самое, что и использование _, за исключением того, что вы его не видите.
tobias_k
8
@Seelenvirtuose Верно, но if(a==1 && a==2 && a==3)не обязательно оценивается одновременно. И это можно использовать, чтобы сделать эту работу, не прибегая к уловкам Unicode.
Эрвин Болвидт

Ответы:

325

Да, это легко сделать с помощью нескольких потоков, если вы объявите переменную aкак volatile.

Один поток постоянно меняет переменную от 1 до 3, а другой поток постоянно проверяет это a == 1 && a == 2 && a == 3. Это происходит достаточно часто, чтобы на консоли печатался непрерывный поток «Success».

(Обратите внимание, что если вы добавите else {System.out.println("Failure");}предложение, вы увидите, что тест проваливается гораздо чаще, чем успешно.)

На практике это также работает, не объявляя aкак изменчивый, но только 21 раз на моем MacBook. Без 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
Это блестящий пример! Я думаю, что украду это в следующий раз, когда я обсуждаю потенциально опасные проблемы параллелизма с младшим разработчиком :)
Алекс Прусс
6
Это маловероятно без volatile, но в принципе это может произойти даже без volatileключевого слова, и это может произойти произвольное количество раз, в то время как, с другой стороны, нет никаких гарантий, что это когда-либо произойдет, даже с volatile. Но, конечно, то, что это происходит на практике, впечатляет…
Хольгер,
5
@AlexPruss Я только что сделал для всех разработчиков, а не только для юниоров в моей компании (я знал, что это возможно), 87% успеха отказов в ответах
Евгений
3
@Holger Правда, никаких гарантий тоже нет. В типичной архитектуре с несколькими ядрами или несколькими процессорами, где оба потока назначены отдельному ядру, вполне вероятно, что это произойдет volatile. Барьеры памяти, созданные для реализации, volatileзамедляют потоки и повышают вероятность их синхронной работы в течение коротких периодов времени. Это происходит гораздо чаще, чем я ожидал. Это очень чувствительно ко времени, но я вижу примерно 0,2–0,8% оценок (a == 1 && a == 2 && a == 3)доходности true.
Эрвин Болвидт
4
Это единственный ответ, который фактически заставляет предполагаемый код возвращать true вместо того, чтобы просто делать небольшие вариации одного и того же. +1
Алехандро
85

Используя понятия (и код) из блестящего гольфа-кода , 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");
    }
}

К сожалению, он не так элегантен, как многопоточный ответ Эрвина Болвидта (так как этот требует Integerкастинга) , но все же происходят некоторые забавные махинации.

phflack
источник
Очень интересно. Я играл с оскорблениями Integer. Обидно в кастинге, но все равно круто.
Майкл
@ Майкл Я не могу понять, как избавиться от кастинга. aустановка на 1/2/3 удовлетворит a == 1, но не пойдет другим путем
phflack
4
Я ответил на этот вопрос несколько дней назад в JS / Ruby / Python и Java. Наименее уродливая версия, которую я мог найти, - это использовать a.equals(1) && a.equals(2) && a.equals(3), что заставляет 1, 2и 3быть автоматически загруженным как Integers.
Эрик Думинил
@EricDuminil Это может немного увлечь, потому что, если бы ты сделал aурок с boolean equals(int i){return true;}?
phflack
1
Как я уже сказал, это наименее уродливо, но все равно не оптимально. С Integer a = 1;этой строкой до сих пор ясно, что на aсамом деле это целое число.
Эрик Думинил
52

В этом вопросе @aioobe предлагает (и не советует) использовать препроцессор Си для классов Java.

Хотя это очень обманчиво, это мое решение:

#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
Pado
источник
6
Такое чувство похоже на прогон кода через поиск и замену до его фактической компиляции, но я определенно мог видеть, что кто-то делает что-то подобное в рамках своего рабочего процесса
phflack
1
@phflack Мне нравится, возможно, этот вопрос о том, чтобы показать опасность предположений при чтении кода. Легко предположить, aчто это переменная, но это может быть любой произвольный фрагмент кода в языках с препроцессором.
Кевин
2
Это не Java. Это язык "Java после передачи через препроцессор C". Подобная лазейка, используемая в этом ответе. (примечание: закулисный сейчас не по теме для Code Golf)
user202729
40

Так как мы уже знаем, что этот код можно сделать истинным благодаря отличным ответам Эрвина Болвидта и phflack , я хотел показать, что вам нужно сохранять высокий уровень внимания при работе с условием, похожим на приведенное в вопросе, так как иногда то, что вы видите, может быть не совсем тем, что вы думаете.

Это моя попытка показать, что этот код печатается Success!на консоли. Я знаю, что немного обманул , но я все еще думаю, что это хорошее место, чтобы представить это прямо здесь.

Неважно, каковы цели написания подобного кода - лучше знать, как справиться со следующей ситуацией и как проверить, не ошибаетесь ли вы с тем, что, по вашему мнению, вы видите.

Я использовал кириллицу «а», которая отличается от латинского «а». Вы можете просмотреть символы, используемые в операторе if здесь .

Это работает, потому что имена переменных взяты из разных алфавитов. Это разные идентификаторы, создающие две разные переменные с разными значениями в каждой.

Обратите внимание, что если вы хотите, чтобы этот код работал должным образом, кодировку символов необходимо изменить на кодировку, поддерживающую оба символа, например все кодировки Unicode (UTF-8, UTF-16 (в BE или LE), UTF-32, даже UTF-7). ) или Windows-1251, ISO 8859-5, KOI8-R (спасибо - Томас Веллер и Паŭло Эберманн - за указание на это):

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

(Надеюсь, вам никогда не придется сталкиваться с подобными проблемами в будущем.)

Пшемыслав Москаль
источник
@ Майкл Что ты имеешь в виду, что не очень хорошо? Я написал это на мобильном телефоне, и здесь он выглядит хорошо для меня, даже при переключении в режим просмотра рабочего стола. Если это поможет улучшить мой ответ, пожалуйста, не стесняйтесь редактировать его, чтобы он был понятен, поскольку сейчас мне это немного сложно.
Przemysław Moskal
@ Майкл Спасибо большое. Я думал, что то, что я признал в конце моего ответа, достаточно :)
Przemysław Moskal
@mohsenmadi Да, я не могу проверить это здесь на мобильном телефоне, но я почти уверен, что перед редактированием последнее предложение было об использовании различных языков в моем примере: P
Przemysław Moskal
Почему █ == 0 должно быть похоже на == 1?
Томас Веллер
1
Более придирчивость: вам не обязательно нужен UTF-8 (хотя это рекомендуется в любом случае), любая кодировка символов, которая имеет и то, аи другое aработает, если вы сообщаете редактору, в какой кодировке он находится (и, возможно, компилятор тоже). Все кодировки Unicode работают (UTF-8, UTF-16 (в BE или LE), UTF-32, даже UTF-7), а также, например, Windows-1251, ISO 8859-5, KOI8-R.
Paŭlo Ebermann
27

Есть другой способ приблизиться к этому (в дополнение к нестабильному подходу к гонке данных, который я опубликовал ранее), используя мощь PowerMock. PowerMock позволяет заменять методы другими реализациями. Когда это сочетается с автоматическим распаковыванием, исходное выражение (a == 1 && a == 2 && a == 3)без изменений может быть выполнено.

Ответ @ phflack основан на изменении процесса автобокса в Java, который использует Integer.valueOf(...)вызов. Приведенный ниже подход основан на изменении автоматической распаковки путем изменения Integer.intValue()вызова.

Преимущество подхода, приведенного ниже, состоит в том, что исходное выражение if, данное OP в этом вопросе, используется без изменений, что я считаю наиболее элегантным.

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 Интересная идея, я понимаю, что вы имеете в виду. Это еще не работает - не ясно, что мешает ему работать.
Эрвин Болвидт
Действительно хорошо, это то, что я пытался сделать, но я обнаружил, что изменение статического метода из неизменяемого класса было бы довольно сложно без манипулирования байт-кодом. Кажется, я был неправ, никогда не слышал о PowerMock, хотя мне интересно, как он это делает. Как примечание, ответ phflack не зависит от автобокса: он изменяет адреса кэшированного Integer (поэтому == фактически сравнивает адреса объекта Integer, а не значения).
Асуб
2
@ Asoub casting ( (Integer)2) упаковывает int. Если взглянуть больше на рефлексию , то похоже, что это невозможно сделать с распаковкой с использованием рефлексии, но вместо этого это возможно с помощью Instrumentation (или с PowerMock, как в этом ответе)
phflack
@Holger, вероятно, не перехватывает синтетический метод, хотя он позволяет мне зарегистрировать замену, access$0и существование метода проверяется при регистрации. Но замена никогда не вызывается.
Эрвин Болвидт
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");
        }
    }
}

По идоне


Но учтите, что это не самое худшее, что вы можете сделать с 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 могут даже не сообщать о каких-либо различиях, всегда находя и то и другое при поиске. У вас может даже быть проблема, что различные представления теряются при копировании исходного кода кому-то еще, возможно, пытаясь получить помощь для «странного поведения», что делает его невоспроизводимым для помощника.

Holger
источник
3
Эх, разве этот ответ не достаточно хорошо описывает юникод?
Майкл
2
int ᅠ2 = 3;это намеренно? Потому что я вижу очень странный код
Рави
1
@ Майкл не совсем, так как этот ответ об использовании двух разных букв (которые IDE считали бы разными при поиске a), в то время как речь идет о пробеле, и, в общем , это заслуга даже более старых ответов на связанный вопрос JavaScript. Обратите внимание, что мой комментарий там даже старше, чем вопрос Java (на несколько дней)…
Хольгер
2
Я не вижу различия. Оба ответа обеспечивают решение, в котором три идентификатора в операторе if визуально похожи, но технически различаются благодаря использованию необычных символов Юникода. Будь то пробел или кириллица не имеет значения.
Майкл
2
@ Майкл, ты можешь видеть это таким образом, это решать тебе. Как уже было сказано, я хотел бы заявить, что ответ, данный пять дней назад для JavaScript, также относится и к Java, просто учитывая историю вопроса. Да, это несколько избыточно для другого ответа, который не относится к связанному вопросу JavaScript. В любом случае, тем временем я обновил свой ответ, добавив случай, который касается не визуально похожих символов, а различных способов кодирования одного и того же символа, и это даже не «необычный символ Юникода»; это персонаж, которым я пользуюсь каждый день ...
Хольгер
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

В том же ключе , заставляя float (или double) потерять (или переполнить) с помощью деления (или умножения) на большое число:

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");
}
Aloke
источник
2
@PatrickRoberts - это специфическое поведение - потеря значения с плавающей запятой согласно документации Java . Также посмотрите это , это , это и это .
Aloke