Чи можете (a == 1 && a == 2 && a == 3) оцінити справжнє в 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

28
@ArthurAttout Не дублікат, це питання стосується Javascript, а не Java.
NOS

13
Той, що використовує пробіли у змінних імен, працює і в Java. Але звичайно це в основному те саме, що використовувати _, за винятком того, що ви не бачите цього.
tobias_k

8
@Seelenvirtuose Істинно, але if(a==1 && a==2 && a==3)не обов'язково оцінювати при цьому. І це можна використати, щоб зробити цю роботу, не вдаючись до хитрощів Unicode.
Ервін Болвідт

Відповіді:


325

Так, досягти цього досить просто за допомогою декількох потоків, якщо ви оголосите змінну aяк мінливу .

Один потік постійно змінює змінну a від 1 до 3, а інший потік постійно тестує це a == 1 && a == 2 && a == 3. Буває досить часто, щоб на консолі був надрукований безперервний потік "Успіх".

(Зверніть увагу, якщо ви додасте 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
Це єдина відповідь, яка фактично змушує задуманий код повернути істину, а не робити лише невеликі варіанти. +1
Алехандро

85

Використовуючи поняття (та код) з блискучої відповіді на гольф коду , Integerзначення можна зіпсувати.

У цьому випадку це може призвести intдо того, що s кастинг Integerбуде рівним, коли вони зазвичай не будуть:

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кастингу) , але все-таки відбуваються веселі шенагігани.


Дуже цікаво. Я грав із зловживаннями Integer. Соромно про кастинг, але це все ще круто.
Майкл

@Michael Я не можу зовсім зрозуміти, як позбутися кастингу. aвстановлення 1/2/3 задовольнить a == 1, але це не піде іншим шляхом
phflack

4
Я відповів на це питання кілька днів тому в JS / Ruby / Python and 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 пропонує (і не радить) використовувати препроцесор C для класів 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

6
Це відчуває себе запуском коду за допомогою пошуку та заміни, перш ніж його фактично компілювати, але я могла точно побачити, як хтось робить щось подібне як частину свого робочого процесу
phflack

1
@phflack Мені це подобається, мабуть, це питання стосується показу небезпеки створення припущень при читанні коду. Неважко припустити a, що це змінна, але це може бути будь-який довільний фрагмент коду на мовах з препроцесором.
кевін

2
Це не Java. Це мова "Java після передачі за допомогою препроцесора C". Подібна лазівка, що використовується в цій відповіді. (зауважте: underhanded - це поза темою для Code Golf зараз)
користувач202729

40

Оскільки ми вже знаємо, що можна змусити цей код оцінити справжній завдяки чудовим відповідям Ервіна Болвідта та phflack , я хотів показати, що вам потрібно підтримувати високий рівень уваги, коли маєте справу зі станом, схожим на представлений у питанні, як іноді те, що ви бачите, може бути не таким, яким ви вважаєте, що це таке.

Це моя спроба показати, що цей код друкується Success!на консолі. Я знаю, що трохи обдурив , але все ще думаю, що це гарне місце, щоб представити його тут.

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

Я використав кирилицю "a", що є відмінним символом від латинського "a". Ви можете перевірити символи, використані в операторі 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!");
        }
    }
}

(Я сподіваюся, що вам ніколи в майбутньому ніколи не доведеться мати справу з такою проблемою.)


@Michael Що ти означає, що він не відображається добре? Я написав це на мобільному телефоні, і мені це здається нормальним, навіть при переході на перегляд робочого столу. Якщо це зробить мою відповідь кращою, будь ласка, не соромтесь відредагувати її.
Перемислав Москаль

@Michael Дуже дякую Я подумав, що те, що я визнав наприкінці своєї відповіді, достатньо :)
Пшемислав Москаль

@mohsenmadi Так, я не можу перевірити це тут на мобільному телефоні, але я впевнений, що перед редагуванням в моєму прикладі останнє речення стосувалось використання різних мов: P
Перемислав Москаль

Чому █ == 0 має бути подібний до a == 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, яке дається ОП у запитанні, використовується незмінним, що, на мою думку, є найбільш елегантним.

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 кастинг ( (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");
        }
    }
}

На Ideone


Але зауважте, що це не найгірше, що ви могли зробити з 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.

На Ideone

Отже, залежно від інструменту, який ви використовуєте, вони можуть виглядати не однаково, текстові інструменти, що підтримуються Unicode, можуть навіть не повідомляти про різницю, завжди знаходячи обидва при пошуку. Можливо, у вас навіть є проблема, що різні уявлення втрачаються під час копіювання вихідного коду на когось іншого, можливо, намагаючись отримати допомогу щодо «дивної поведінки», що робить його невідтворюваним для помічника.


3
Е, чи не відповідає ця відповідь зловживанням unicode досить добре?
Майкл

2
int ᅠ2 = 3;це навмисно? Тому що я бачу дуже дивний код навколо
Раві,

1
@Michael не зовсім так, оскільки ця відповідь стосується використання двох різних літер (які IDE вважали б різними при пошуку a), тоді як мова йде про пробіли, і це добре, що вона дає заслугу ще старшим відповідям на відповідне питання JavaScript. Зауважте, що мій коментар там навіть старіший за питання про Java (на кілька днів)…
Холгер

2
Я не бачу різниці. Обидві відповіді пропонують рішення, коли три ідентифікатори в операторі if візуально схожі, але технічно відрізняються завдяки використанню незвичних символів unicode. Будь то пробіл чи кирилиця - не має значення.
Михайло

2
@Michael ти можеш бачити це саме так, це залежить від тебе. Як було сказано, я хотів сказати, що відповідь, надана п’ять днів тому для JavaScript, стосується і Java, лише враховуючи історію питання. Так, це дещо зайве для іншої відповіді, яка не стосується пов'язаного питання JavaScript. У будь-якому випадку я тим часом оновив свою відповідь, щоб додати випадок, який стосується не візуально схожих символів, а різних способів кодування одного і того ж символу, і це навіть не «незвичайний символ Unicode»; це персонаж, яким я користуюся щодня…
Хольгер

4

Надихнувшись чудовою відповіддю @ Ервіна , я написав подібний приклад, але використовуючи API Java Stream .

І цікаво, що моє рішення працює, але в дуже рідкісних випадках (адже   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 . Також дивіться це , це , це та це .
Aloke
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.