Почему это происходит в бесконечном цикле?

У меня есть следующий код:

public class Tests {
    public static void main(String[] args) throws Exception {
        int x = 0;
        while(x<3) {
            x = x++;
            System.out.println(x);
        }
    }
}

Мы знаем, что он должен был писать только x++ или x=x+1, но в x = x++ он должен сначала атрибут x сам, а затем увеличивать его. Почему x продолжает 0 как значение?

- обновление

Здесь байт-код:

public class Tests extends java.lang.Object{
public Tests();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[])   throws java.lang.Exception;
  Code:
   0:   iconst_0
   1:   istore_1
   2:   iload_1
   3:   iconst_3
   4:   if_icmpge   22
   7:   iload_1
   8:   iinc    1, 1
   11:  istore_1
   12:  getstatic   #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   15:  iload_1
   16:  invokevirtual   #3; //Method java/io/PrintStream.println:(I)V
   19:  goto    2
   22:  return

}

Я прочитаю инструкции чтобы попытаться понять...

467
задан 30 сент. '10 в 17:07
источник поделиться
27 ответов

Примечание. Первоначально я разместил код С# в этом ответе для иллюстрации, поскольку С# позволяет передавать параметры int по ссылке с ключевым словом ref. Я решил обновить его с помощью реального юридического кода Java, используя первый класс MutableInt, который я нашел в Google, чтобы приблизиться к тому, что ref в С#. Я не могу сказать, помогает ли это или причиняет боль. Я скажу, что лично я не сделал столько развития Java; поэтому для всех, кого я знаю, может быть гораздо больше идиоматических способов проиллюстрировать этот момент.


Возможно, если мы выпишем метод для выполнения эквивалента того, что x++ сделает это более понятным.

public MutableInt postIncrement(MutableInt x) {
    int valueBeforeIncrement = x.intValue();
    x.add(1);
    return new MutableInt(valueBeforeIncrement);
}

Правильно? Увеличьте пройденное значение и верните исходное значение: это определение оператора postincrement.

Теперь посмотрим, как это поведение проявляется в вашем примере кода:

MutableInt x = new MutableInt();
x = postIncrement(x);

postIncrement(x) делает что? Приращения x, да. И затем возвращает значение x до инкремента. Это возвращаемое значение затем присваивается x.

Таким образом, порядок значений, назначенных x равен 0, то 1, то 0.

Это может быть еще яснее, если мы перепишем выше:

MutableInt x = new MutableInt();    // x is 0.
MutableInt temp = postIncrement(x); // Now x is 1, and temp is 0.
x = temp;                           // Now x is 0 again.

Ваша фиксация на том факте, что при замене x в левой части вышеприведенного назначения на y, вы можете видеть, что он сначала увеличивает x, а затем присваивает ему значение y ", меня поражает. Не x присваивается y; это значение, ранее присвоенное x. Действительно, инъекция y делает вещи, отличные от сценария выше; мы просто получили:

MutableInt x = new MutableInt();    // x is 0.
MutableInt y = new MutableInt();    // y is 0.
MutableInt temp = postIncrement(x); // Now x is 1, and temp is 0.
y = temp;                           // y is still 0.

Итак, ясно: x = x++ эффективно не меняет значение x. Это всегда приводит к тому, что x имеет значения x 0, затем x 0 + 1, а затем x 0 снова.


Обновить. Кстати, чтобы вы не сомневались, что x когда-либо присваивается 1 "между" операцией приращения и присваиванием в приведенном выше примере, я собрал быструю демоверсию, чтобы проиллюстрировать что это промежуточное значение действительно "существует", хотя оно никогда не будет "замечено" в исполняемом потоке.

Демонстрационные вызовы x = x++; в цикле, в то время как отдельный поток непрерывно печатает значение x на консоли.

public class Main {
    public static volatile int x = 0;

    public static void main(String[] args) {
        LoopingThread t = new LoopingThread();
        System.out.println("Starting background thread...");
        t.start();

        while (true) {
            x = x++;
        }
    }
}

class LoopingThread extends Thread {
    public @Override void run() {
        while (true) {
            System.out.println(Main.x);
        }
    }
}

Ниже приведена выдержка из вышеприведенного вывода программы. Обратите внимание на нерегулярное появление как 1s, так и 0s.

Starting background thread...
0
0
1
1
0
0
0
0
0
0
0
0
0
0
1
0
1
343
ответ дан 30 сент. '10 в 18:09
источник

x = x++ работает следующим образом:

  • Сначала он вычисляет выражение x++. Оценка этого выражения создает значение выражения (которое является значением x до приращения) и увеличивает x.
  • Позже он присваивает значение выражения x, переписывая увеличиваемое значение.

Итак, последовательность событий выглядит следующим образом (это фактический декомпилированный байт-код, созданный javap -c, с моими комментариями):

   8:   iload_1         // Remember current value of x in the stack
   9:   iinc    1, 1    // Increment x (doesn't change the stack)
   12:  istore_1        // Write remebered value from the stack to x

Для сравнения x = ++x:

   8:   iinc    1, 1    // Increment x
   11:  iload_1         // Push value of x onto stack
   12:  istore_1        // Pop value from the stack to x
166
ответ дан 30 сент. '10 в 17:13
источник

Это происходит потому, что значение x вообще не увеличивается.

x = x++;

эквивалентно

int temp = x;
x++;
x = temp;

Пояснение:

Посмотрите на байтовый код для этой операции. Рассмотрим образец класса:

class test {
    public static void main(String[] args) {
        int i=0;
        i=i++;
    }
}

Теперь, используя класс disassembler, мы получим:

$ javap -c test
Compiled from "test.java"
class test extends java.lang.Object{
test();
  Code:
   0:    aload_0
   1:    invokespecial    #1; //Method java/lang/Object."<init>":()V
   4:    return

public static void main(java.lang.String[]);
  Code:
   0:    iconst_0
   1:    istore_1
   2:    iload_1
   3:    iinc    1, 1
   6:    istore_1
   7:    return
}

Теперь Java VM является стеком, который означает для каждой операции, данные будут помещены в стек, а из стека будут выведены данные чтобы выполнить операцию. Существует также другая структура данных, обычно это массив для хранения локальных переменных. Локальным переменным присваиваются идентификаторы, которые являются только индексами для массива.

Давайте посмотрим на мнемонику в методе main():

  • iconst_0: постоянное значение 0 вставляется в стек.
  • istore_1: верхний элемент стек выгружается и сохраняется в локальная переменная с индексом 1, которая x.
  • iload_1: значение на location 1, то есть значение x который 0, вставляется в стек.
  • iinc 1, 1: значение на расположение памяти 1 увеличивается на 1. Итак, x теперь становится 1.
  • istore_1: значение в верхней части стек хранится в памяти местоположение 1. То есть 0 до x переписывать его добавочное значение.

Следовательно, значение x не изменяется, что приводит к бесконечному циклу.

104
ответ дан 30 сент. '10 в 17:15
источник
  • Префиксная нотация будет увеличивать переменную до того, как выражение будет оценено.
  • Постфиксная нотация будет увеличиваться ПОСЛЕ оценки выражения.

Однако "=" имеет более низкий приоритет оператора, чем "++".

So x=x++; следует оценивать следующим образом

  • x подготовлен для присвоения (оценен)
  • x incremented
  • Предыдущее значение x, присвоенное x.
52
ответ дан 30 сент. '10 в 17:27
источник

Ни один из ответов, где достаточно места, так вот:

Когда вы пишете int x = x++, вы не присваиваете x самому себе новое значение, вы присваиваете x возвращаемое значение выражения x++. Который является исходным значением x, как указано в Колине Кокрене.

Для удовольствия проверьте следующий код:

public class Autoincrement {
        public static void main(String[] args) {
                int x = 0;
                System.out.println(x++);
                System.out.println(x);
        }
}

Результат будет

0
1

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

34
ответ дан 30 сент. '10 в 17:37
источник

Это уже хорошо объяснялось другим. Я просто включаю ссылки на соответствующие разделы спецификации Java.

x = x ++ - выражение. Java будет следовать порядку оценки. Сначала он оценит выражение x ++, которое увеличит x и установит значение результата в предыдущее значение x. Затем присвойте результат выражения переменной x. В конце x возвращается к своему предыдущему значению.

29
ответ дан 30 сент. '10 в 18:33
источник

Это утверждение:

x = x++;

оценивается следующим образом:

  • Нажмите x в стек;
  • Приращение x;
  • Поп x из стека.

Таким образом, значение не изменяется. Сравните это с:

x = ++x;

который оценивается как:

  • Приращение x;
  • Нажмите x в стек;
  • Поп x из стека.

Что вы хотите:

while (x < 3) {
  x++;
  System.out.println(x);
}
18
ответ дан 30 сент. '10 в 17:10
источник

Ответ довольно прост. Это связано с оценкой вещей. x++ возвращает значение x, а затем увеличивает x.

Следовательно, значение выражения x++ равно 0. Поэтому вы назначаете x=0 каждый раз в цикле. Конечно, x++ увеличивает это значение, но это происходит до назначения.

10
ответ дан 02 окт. '10 в 1:18
источник

От http://download.oracle.com/javase/tutorial/java/nutsandbolts/op1.html

Операторы приращения/декремента могут применяется до (префикс) или после (postfix) операндом. Код Результат ++; и ++ результат; обе в результате увеличивается на единицу. Единственное отличие состоит в том, что префикс версия (результат ++) оценивает увеличенное значение, , тогда как постфиксная версия (результат ++) оценивает до исходного значения. Если ты просто выполняя простой приращение/декремент, на самом деле это не так вопрос, какую версию вы выберете. Но если вы используете этот оператор в части большее выражение, то, которое вы выбор может сделать значительный разница.

Чтобы проиллюстрировать, попробуйте следующее:

    int x = 0;
    int y = 0;
    y = x++;
    System.out.println(x);
    System.out.println(y);

Что будет печатать 1 и 0.

8
ответ дан 30 сент. '10 в 17:27
источник

Вы эффективно получаете следующее поведение.

  • возьмите значение x (которое равно 0) как "результат" правой стороны
  • увеличивать значение x (так что x теперь 1)
  • назначить результат правой стороны (который был сохранен как 0) на x (x теперь 0)

Идея заключается в том, что оператор post-increment (x ++) увеличивает эту переменную AFTER, возвращая ее значение для использования в используемом в ней уравнении.

Изменить: добавление небольшого бита из-за комментария. Рассмотрим это следующим образом.

x = 1;        // x == 1
x = x++ * 5;
              // First, the right hand side of the equation is evaluated.
  ==>  x = 1 * 5;    
              // x == 2 at this point, as it "gave" the equation its value of 1
              // and then gets incremented by 1 to 2.
  ==>  x = 5;
              // And then that RightHandSide value is assigned to 
              // the LeftHandSide variable, leaving x with the value of 5.
7
ответ дан 30 сент. '10 в 17:16
источник

Вам действительно не нужен машинный код, чтобы понять, что происходит.

В соответствии с определениями:

  • Оператор присваивания вычисляет выражение правой стороны и сохраняет его во временной переменной.

    1,1. Текущее значение x копируется в эту временную переменную

    1,2. x теперь увеличивается.

  • Затем временная переменная копируется в левую часть выражения, что является случайным! Итак, почему старое значение x снова копируется в себя.

Это довольно просто.

7
ответ дан 01 окт. '10 в 7:10
источник

Это потому, что в этом случае он никогда не увеличивается. x++ будет использовать значение его сначала перед приращением, как в этом случае, это будет выглядеть так:

x = 0;

Но если вы сделаете ++x;, это увеличится.

5
ответ дан 30 сент. '10 в 17:11
источник

Значение остается равным 0, так как значение x++ равно 0. В этом случае не имеет значения, увеличивается ли значение x или нет, выполняется назначение x=0. Это перезапишет временное увеличенное значение x (которое было 1 за "очень короткое время" ).

3
ответ дан 30 сент. '10 в 17:15
источник

Приговор

x = x++;

"переводит" на

x = x;
x = x + 1;

Что это.

1
ответ дан 12 сент. '16 в 8:17
источник

Это работает так, как вы ожидаете другого. Это разница между префиксным и постфиксным.

int x = 0; 
while (x < 3)    x = (++x);
1
ответ дан 30 сент. '10 в 17:39
источник

Когда значение ++ находится на rhs, результат возвращается до того, как число будет увеличено. Измените на ++ x, и все было бы хорошо. Java оптимизировала бы это, чтобы выполнить одну операцию (назначение x на x), а не прирост.

1
ответ дан 01 окт. '10 в 2:15
источник

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

В частности, выражение "x ++" имеет значение "x" перед приращением, а не "++ x", которое имеет значение "x" после инкрементации.

Если вы заинтересованы в исследовании байт-кода, мы рассмотрим три строки:

 7:   iload_1
 8:   iinc    1, 1
11:  istore_1

7: iload_1 # Поместит значение 2-й локальной переменной в стек
8: iinc 1,1 # будет увеличивать 2-ю локальную переменную с 1, обратите внимание, что она оставляет стек нетронутым!
9: istore_1 # Попадет верх стека и сохранит значение этого элемента во вторую локальную переменную
(Вы можете прочитать эффекты каждой команды JVM здесь)

Вот почему приведенный выше код будет зацикливаться на неопределенный срок, тогда как версия c++ x не будет. Байт-код для ++ x должен выглядеть совсем по-другому, насколько я помню из компилятора 1.3 Java, который я написал чуть более года назад, байт-код должен выглядеть примерно так:

iinc 1,1
iload_1
istore_1

Таким образом, просто заменяя две первые строки, изменяется семантика так, что значение, оставшееся в верхней части стека, после инкремента (то есть "значение" выражения) является значением после приращения.

1
ответ дан 02 окт. '10 в 4:16
источник

Подумайте о том, что x ++ является вызовом функции, который "возвращает" то, что X было до приращения (что вызвало его пошаговое увеличение).

Итак, порядок операций:
1: кешировать значение x до приращения 2: приращение x
3: вернуть кешированное значение (x до его увеличения)
4: возвращаемое значение присваивается x

1
ответ дан 30 сент. '10 в 17:16
источник
    x++
=: (x = x + 1) - 1

Итак:

   x = x++;
=> x = ((x = x + 1) - 1)
=> x = ((x + 1) - 1)
=> x = x; // Doesn't modify x!

В то время как

   ++x
=: x = x + 1

Итак:

   x = ++x;
=> x = (x = x + 1)
=> x = x + 1; // Increments x

Конечно, конечный результат такой же, как только x++; или ++x; в строке.

1
ответ дан 26 нояб. '11 в 14:08
источник

Прежде чем увеличивать значение на единицу, значение присваивается переменной.

0
ответ дан 01 окт. '10 в 10:59
источник

Это происходит, потому что он пост увеличивается. Это означает, что переменная увеличивается при вычислении выражения.

int x = 9;
int y = x++;

x теперь 10, но y равно 9, значение x до того, как оно было увеличено.

См. больше в Определение пост-приращения.

0
ответ дан 01 окт. '10 в 20:23
источник

Выражение x++ оценивается как x. Часть ++ влияет на значение после оценки, а не после утверждения. поэтому x = x++ эффективно преобразуется в

int y = x; // evaluation
x = x + 1; // increment part
x = y; // assignment
0
ответ дан 01 окт. '10 в 19:38
источник
 x = x++; (increment is overriden by = )

из-за вышеприведенного утверждения x никогда не достигает 3;

0
ответ дан 30 сент. '10 в 20:30
источник

Я думаю, потому что в Java++ имеет более высокий приоритет, чем = (присвоение)... Это так? Посмотрите http://www.cs.uwf.edu/~eelsheik/cop2253/resources/op_precedence.html...

То же самое, если вы пишете x = x + 1... +, имеет более высокий приоритет, чем = (присвоение)

0
ответ дан 01 окт. '10 в 10:00
источник

Проверьте приведенный ниже код,

    int x=0;
    int temp=x++;
    System.out.println("temp = "+temp);
    x = temp;
    System.out.println("x = "+x);

вывод будет,

temp = 0
x = 0

post increment означает увеличивать значение и возвращать значение до приращения. Поэтому значение temp равно 0. Так что, если temp = i и это в цикле (кроме первой строки кода). как в вопросе!!!!

0
ответ дан 29 мая '15 в 11:31
источник

Интересно, есть ли что-нибудь в спецификации Java, которая точно определяет поведение этого. (Очевидно, что это утверждение означает, что я слишком ленив, чтобы проверить.)

Примечание из байт-кода Tom, ключевые строки - 7, 8 и 11. Линия 7 загружает x в стек вычислений. Строка 8 увеличивает x. Строка 11 сохраняет значение из стека обратно в x. В обычных случаях, когда вы не присваиваете значения себе сами, я не думаю, что была бы причина, по которой вы не могли бы загружать, хранить, а затем увеличивать. Вы получите тот же результат.

Например, предположим, что у вас был более нормальный случай, когда вы написали что-то вроде:   г = (х ++) + (у ++);

Скажите ли это (псевдокод пропустить технику)

load x
increment x
add y
increment y
store x+y to z

или

load x
add y
store x+y to z
increment x
increment y

не имеет значения. Любая реализация должна быть действительной, я бы подумал.

Я был бы очень осторожен в написании кода, который зависит от этого поведения. Он выглядит очень зависимым от реализации, между-трещинами в спецификациях для меня. Единственный раз, когда это имело бы значение, - если вы сделали что-то сумасшедшее, например, здесь, или если у вас было два потока, и они зависели от порядка оценки внутри выражения.

0
ответ дан 30 сент. '10 в 22:55
источник

Оператор приращения применяется к той же переменной, которую вы назначаете. Это требует неприятностей. Я уверен, что вы можете увидеть значение переменной x во время выполнения этой программы..., что должно дать понять, почему цикл никогда не заканчивается.

-1
ответ дан 10 дек. '10 в 14:29
источник

Другие вопросы по меткам или Задайте вопрос