Как переменная Java может отличаться от самой себя?

106

Мне интересно, можно ли решить этот вопрос на Java (я новичок в этом языке). Это код:

class Condition {
    // you can change in the main
    public static void main(String[] args) { 
        int x = 0;
        if (x == x) {
            System.out.println("Ok");
        } else {
            System.out.println("Not ok");
        }
    }
}

В своей лаборатории я получил следующий вопрос: как можно пропустить первый случай (т.е. сделать x == xусловие ложным), не изменяя само условие?

Хусам
источник
12
Я считаю, что ограничений должно быть больше, иначе они будут слишком открытыми.
Маленький ученик Ферма
52
Это так просто, как System.out.println("Gotcha!");вместо комментария? :)
stuXnet 04
7
Хорошо, тогда это double a = Double.NaN самый короткий ответ, и мой "хак - это просто чит;"
Кристиан Кютбах
47
Это симпатичная мелочь, связанная с Java, но я надеюсь, что никто не станет рассматривать ее как вопрос на собеседовании. Люди, рассматривающие кандидатов на работу, должны делать все возможное, чтобы выяснить, разбирается ли кандидат в программировании, а не сколько мелочей он накопил. Я почти не использовал числа с плавающей запятой за 17 лет программирования на Java, не говоря уже о конструкции NaN, НАМНОГО меньше зная, как она ведет себя с оператором == ...
arcy
8
@ user1158692 Собственно говоря, я лично ненавижу любые программы, в которых основные операторы были переопределены, и рад, что любой код, который я получаю на java, не был испорчен (я видел * переопределение как векторное перекрестное произведение и скалярное произведение, потому что оба вида умножения векторов; бесит! В то время как в java один вызывается, .cross()а другой есть, .dot()и нет никакой путаницы. Также тот факт, что «переопределить оператор == и всегда возвращать false» не может произойти, кажется про java
Ричард Тингл

Ответы:

172

Один простой способ - использовать Float.NaN:

float x = Float.NaN;  // <--

if (x == x) {
    System.out.println("Ok");
} else {
    System.out.println("Not ok");
}
Не в порядке

Вы можете сделать то же самое с Double.NaN.


Из JLS §15.21.1. Операторы числового равенства ==и!= :

Проверка на равенство с плавающей запятой выполняется в соответствии с правилами стандарта IEEE 754:

  • Если один из операндов равен NaN, то результат ==равен, falseно результат !=равен true.

    В самом деле, проверка x!=xвыполняется trueтогда и только тогда, когда значение xравно NaN.

...

аршаджи
источник
157
int x = 0;
if (x == x) {
    System.out.println("Not ok");
} else {
    System.out.println("Ok");
}
Йерун Ванневель
источник
63
Хех, это полностью отвечает на заданный вопрос.
Дэйв Ньютон
5
@AswinMurugesh Верно, но если мы воспринимаем вопрос полностью буквально, как этот ответ, то мы можем полностью удалить его else. Технически это не нарушает условия вопроса.
arshajii
67
Учитывая, что это мой второй по количеству голосов ответ, я не уверен, делать ли я вывод, что я очень забавный или дрянной программист.
Jeroen Vannevel
5
@JeroenVannevel Учитывая требования, я думаю, что это наиболее подходящий ответ KISS /
YAGNI
12
@jddsantaella: очевидно, это было потом отредактировано. В исходном вопросе говорилось: «Как я могу напечатать« не нормально »».
Йерун Ванневель,
147

По спецификациям языка Java NaN не равно NaN.

Следовательно, любая строка, которая вызывает xравенство NaN, вызовет это, например

double x=Math.sqrt(-1);

Из спецификации языка Java:

Операторы с плавающей точкой не вызывают исключений (§11). Операция с переполнением приводит к бесконечности со знаком, операция с обратным заполнением дает денормализованное значение или нулевой знак со знаком, а операция, не имеющая математически определенного результата, дает NaN. Все числовые операции с NaN в качестве операнда в результате дают NaN. Как уже было описано, NaN неупорядочено, поэтому операция числового сравнения с одним или двумя NaN возвращает false, а любое! = Сравнение с участием NaN возвращает true, включая x! = X, когда x равно NaN.

Ричард Тингл
источник
@ sᴜʀᴇsʜᴀᴛᴛᴀ Справедливый вопрос, я был так занят, собираясь найти "соглашение о кодировании", что забыл ответить на вопрос,
Ричард Тингл
Это действительно только в том случае, если a объявлено как Object или double.
Christian Kuetbach
1
@ChristianKuetbach Верно, в отсутствие какой-либо информации об обратном я предположил, что закомментированная строка может быть чем угодно
Ричард Тингл
2
Даже мой обманчивый ответ верен и соответствует правилам. Я редактировал только перед оператором if, и распечатывается только «Попался!». Я уверен, что этот ответ не является ответом в сознании создателя этой загадки. Но загадка не совсем определена (как большинство программных проектов ).
Christian Kuetbach
73

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

Вот короткая демонстрация:

class Test {

    static int x = 0;

    public static void main(String[] args) throws Exception {

        Thread t = new Thread(new Change());
        t.setDaemon(true);
        t.start();

        while (true) {
            if (x == x) {
                System.out.println("Ok");
            } else {
                System.out.println("Not ok");
                break;
            }
        }
    }
}

class Change implements Runnable {
    public void run() {
        while (true)
            Test.x++;
    }
}

Вывод:


Ok
Ok
Ok
Ok
Ok
Ok
Ok
Ok
Not ok
Пшемо
источник
8
Ха-ха +1 за усилие, но человек ... никто из моей лаборатории Java не мог бы придумать что-то подобное (включая инструктора).
Уильям Галл
28
@WilliamGaul Правда? Я всегда думал, что это один из основных примеров, показывающих возможные проблемы с многопоточностью и почему люди, которые думают, что эта тема проста, никогда не должны ни за что отвечать :)
Pshemo
4
Я даже не думал об этом, пока не прочитал ваш ответ. Спасибо, что добавили это в мой мысленный инструментарий :)
Behe
56

Замененная строка могла читать.

double x = Double.NaN;

Это приведет к печати ошибки.

Спецификация языка Java (JLS) гласит:

Операторы с плавающей точкой не вызывают исключений (§11). Операция с переполнением приводит к бесконечности со знаком, операция с обратным заполнением дает денормализованное значение или нулевой знак со знаком, а операция, не имеющая математически определенного результата, дает NaN. Все числовые операции с NaN в качестве операнда в результате дают NaN. Как уже было описано, NaN неупорядочено, поэтому операция числового сравнения с одним или двумя NaN возвращает false, а любое! = Сравнение с участием NaN возвращает true, включая x! = X, когда x равно NaN.

Мексика
источник
Или привести к ошибке компиляции, если a объявлено как String a = "Nope"; Вот почему я попросил типа «а»
Кристиан Куетбах
Приведенный выше код не дает информации о типах, поэтому я предположил, что a еще не определен.
Mex
Я думаю, что ваш ответ и есть ответ, который был в уме создателя загадки. Но правила были непонятны. Были даны только два правила: 1. вставлять только в строку с комментарием и 2. получать только одно напечатанное «Попался!»
Кристиан Кютбах
автор загадки мог сделать всегда актуальным, заключив код в {}
Мексика
30

Мне удалось получить Gotcha!от этого:

volatile Object a = new Object();

class Flipper implements Runnable {
  Object b = new Object();

  public void run() {
    while (true)  {
      Object olda = a;
      a = b;
      a = olda;
    }
  }

}

public void test() {
  new Thread(new Flipper()).start();

  boolean gotcha = false;
  while (!gotcha) {
    // I've added everything above this - I would therefore say still legal.
    if (a == a) {
      System.out.println("Not yet...");
    } else {
      System.out.println("Gotcha!");
      // Uncomment this line when testing or you'll never terminate.
      //gotcha = true;
    }
  }
}
Старый
источник
1
Это меняет не только комментарий, как требует вопрос.
Mex
Я пробовал что-то подобное, но я думаю, что это гарантировано всегда будет давать один и тот же результат, не так ли?
AndreDurao
4
@Mex - это действительно так, но он сохраняет достаточно оригинала, чтобы продемонстрировать еще один ключевой момент, который иногда a != aиз-за того, что он был изменен другим потоком. Я подозреваю, что это принесет очки в интервью.
OldCurmudgeon
На самом деле это довольно умно, я предполагаю, что это работает, «надеясь», что aэто было изменено первым потоком между первым и вторым доступом для сравнения
Ричард Тингл
4
Повторите своих сомневающихся; Стоит отметить, что все, что вы написали над ключевым ifутверждением, при необходимости можно было бы записать одной ужасной строкой
Ричард Тингл
25

Есть так много решений:

class A extends PrintStream {
    public A(PrintStream x) {super(x);}
    public void println(String x) {super.println("Not ok");}
    public static void main(String[] args) {
        System.setOut(new A(System.out));
        int x = 0;
        if (x == x) {
            System.out.println("Ok");
        } else {
            System.out.println("Not ok");
        }
    }
}
Йоханнес Кун
источник
1
Если я чего-то super.printlnне упускаю , это должно быть «Не в порядке», верно?
Izkata
@Izkata Да, не перепроверил, каким должен быть желаемый результат.
Йоханнес Кун
2
Абсолютно блестящий!
Дариуш
25

Одно простое решение:

System.out.println("Gotcha!");if(false)
if( a == a ){
  System.out.println("Not yet...");
} else {
  System.out.println("Gotcha!");
}

Но я не знаю всех правил этой загадки ...

:) Знаю, что это чит, но не зная всех правил, это самое простое решение вопроса :)

Кристиан Куетбах
источник
1
Можно написать в одну строку;)
Кристиан Кютбах 04
6
@ChristianKuetbach Как и все программы
Ричард Тингл
2
@ChristianKuetbach Честно говоря, компилятор должен немедленно удалить все файлы на вашем компьютере, если вы действительно пытались так программировать
Ричард Тингл
1
Зачем так сложно? if (System.out.println("Gotcha") && false)
Alexis
3
ошибка: тип 'void' здесь недопустим, если (System.out.println ("Gotcha") && false) Ваш код не компилируется ...
Кристиан Куетбах
11

Создайте свой собственный класс Systemв одном пакете с Condition.
В этом случае ваш Systemкласс будет скрывать java.lang.Systemкласс

class Condition
{
    static class System
    {
        static class out
        {
            static void println(String ignored)
            {
                java.lang.System.out.println("Not ok");
            }
        }
    }

    public static void main (String[] args) throws java.lang.Exception
    {
        int x = 0;
        if (x == x) 
        {
           System.out.println("Not ok");
        } 
        else 
        {
           System.out.println("Ok");
        }
    }
}  

Ideone DEMO

Илья
источник
9

Используя тот же подход с пропуском / изменением вывода из других ответов:

class Condition {
    public static void main(String[] args) {
        try {
            int x = 1 / 0;
            if (x == x) {
                System.out.println("Ok");
            } else {
                System.out.println("Not ok");
            }
        } catch (Exception e) {
            System.out.println("Not ok");
        }
    }
}
Хигуаро
источник