Как переменная 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");
        }
    }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.