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

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

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
задан Tom Brito 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
ответ дан Dan Tao 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
ответ дан axtavt 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
ответ дан codaddict 30 сент. '10 в 17:15
источник поделиться
  • Префиксная нотация будет увеличивать переменную до того, как выражение будет оценено.
  • Постфиксная нотация будет увеличиваться ПОСЛЕ оценки выражения.

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

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

  • x подготовлен для присвоения (оценен)
  • x incremented
  • Предыдущее значение x, присвоенное x.
52
ответ дан Jaydee 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
ответ дан Robert Munteanu 30 сент. '10 в 17:37
источник поделиться

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

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

29
ответ дан plodoc 30 сент. '10 в 18:33
источник поделиться

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

x = x++;

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

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

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

x = ++x;

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

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

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

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

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

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

10
ответ дан Mike Jones 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
ответ дан Colin Cochrane 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
ответ дан RHSeeger 30 сент. '10 в 17:16
источник поделиться

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

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

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

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

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

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

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

7
ответ дан houman001 01 окт. '10 в 7:10
источник поделиться

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

x = 0;

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

5
ответ дан mezzie 30 сент. '10 в 17:11
источник поделиться

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

3
ответ дан Progman 30 сент. '10 в 17:15
источник поделиться

Приговор

x = x++;

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

x = x;
x = x + 1;

Что это.

1
ответ дан Willmore 12 сент. '16 в 8:17
источник поделиться

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

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

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

1
ответ дан Steven 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
ответ дан micdah 02 окт. '10 в 4:16
источник поделиться

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

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

1
ответ дан jhabbott 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
ответ дан Paulpro 26 нояб. '11 в 14:08
источник поделиться

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

0
ответ дан kumara 01 окт. '10 в 10:59
источник поделиться

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

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

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

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

0
ответ дан BrunoBrito 01 окт. '10 в 20:23
источник поделиться

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

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

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

0
ответ дан Praveen Prasad 30 сент. '10 в 20:30
источник поделиться

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

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

0
ответ дан cubob 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
ответ дан prime 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
ответ дан Jay 30 сент. '10 в 22:55
источник поделиться

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

-1
ответ дан SIVAKUMAR.J 10 дек. '10 в 14:29
источник поделиться

Другие вопросы по меткам