502

次のコードがあります。

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

x++彼がorだけを書くべきx=x+1だったことはわかっていますが、x = x++最初にそれ自体に属性xを付け、後でそれをインクリメントする必要があります。as 値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

}

理解するために指示について読んでみます...

4

26 に答える 26

360

:C#ではキーワードintを使用して参照によってパラメーターを渡すことができるため、最初は説明のためにこの回答にC#コードを投稿しました。ref私はMutableInt、Google で見つけた最初のクラスを使用して、実際の合法的な Java コードで更新しrefて、C# での動作を近似することにしました。それが答えを助けるか傷つけるかは本当にわかりません。個人的には、Java 開発はあまり行っていません。したがって、私が知っている限りでは、この点を説明するもっと慣用的な方法があるかもしれません.


おそらく、それが行うことと同等のことを行うメソッドを書き出すx++と、これがより明確になるでしょう。

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

右?渡された値をインクリメントし、元の値を返します。これがポストインクリメント演算子の定義です。

それでは、サンプル コードでこの動作がどのように実行されるかを見てみましょう。

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になります。


更新:ちなみに、上記の例のインクリメント操作と割り当ての「間」で 1 に割り当てられることを疑うxために、この中間値が実際に「存在する」ことを示す簡単なデモをまとめました。実行中のスレッドで「見られる」ことはありません。

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);
        }
    }
}

以下は、上記のプログラムの出力の抜粋です。1 と 0 の両方が不規則に発生することに注意してください。

バックグラウンド スレッドを開始しています...
0
0
1
1
0
0
0
0
0
0
0
0
0
0
1
0
1
于 2010-09-30T15:09:25.593 に答える
175

x = x++次のように動作します。

  • まず、式を評価しますx++。この式を評価すると、式の値 (xインクリメント前の値) とインクリメントが生成されますx
  • 後で式の値を に代入しx、インクリメントされた値を上書きします。

したがって、一連のイベントは次のようになります (これは、 によって生成された実際の逆コンパイルされたバイトコードでjavap -cあり、私のコメントが含まれています)。

   8: iload_1 // スタック内の x の現在の値を記憶する
   9: iinc 1, 1 // x をインクリメントします (スタックは変更しません)
   12: istore_1 // 記憶された値をスタックから x に書き込みます

比較のために、x = ++x:

   8: iinc 1, 1 // x をインクリメントする
   11: iload_1 // x の値をスタックにプッシュする
   12: istore_1 // スタックから x に値をポップする
于 2010-09-30T14:13:44.087 に答える
106

これは、 の値xがまったくインクリメントされないために発生します。

x = x++;

と同等です

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

説明:

この操作のバイトコードを見てみましょう。サンプルクラスを考えてみましょう:

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

これでクラス逆アセンブラを実行すると、次のようになります。

$ 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はスタック ベースです。つまり、操作ごとに、データがスタックにプッシュされ、スタックからデータがポップアウトされて操作が実行されます。別のデータ構造もあり、通常はローカル変数を格納する配列です。ローカル変数には、配列への単なるインデックスである ID が与えられます。

メソッドのニーモニックを見てみましょう。main()

  • iconst_0: 定数値0 がスタックにプッシュされます。
  • istore_1: スタックの一番上の要素がポップアウトされ、インデックスが のローカル変数に格納され 1
    ますx
  • iload_1: の値が である位置1の値がx0スタックにプッシュされます。
  • iinc 1, 1: メモリ位置の値1が だけインクリメントされ1ます。だからx今になり 1ます。
  • istore_1: スタックの最上位の値がメモリ ロケーションに格納されます1。つまり、インクリメントされた値を上書きする0ために割り当てられます。x

したがって、 の値はx変化せず、無限ループが発生します。

于 2010-09-30T14:15:55.407 に答える
54
  1. プレフィックス表記は、式が評価される前に変数をインクリメントします。
  2. 後置表記は、式の評価後にインクリメントされます。

ただし、「=」の演算子の優先順位は「」よりも低くなり++ます。

したがってx=x++;、次のように評価する必要があります

  1. x割り当ての準備(評価済み)
  2. xインクリメント
  3. xに割り当てられた以前の値x
于 2010-09-30T14:27:49.500 に答える
34

答えはどれもまったく当てはまらないので、ここに行きます:

あなたが書いているときint x = x++、あなたはx新しい値でそれ自身であることを代入しているのではなくx、式の戻り値であることを代入していx++ます。Colin Cochrane's answerxで示唆されているように、これはたまたま の元の値です。

楽しみのために、次のコードをテストしてください。

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

結果は次のようになります。

0
1

式の戻り値は、xゼロである の初期値です。しかし後で、 の値を読み取るとx、更新された値、つまり 1 を受け取ります。

于 2010-09-30T14:37:22.957 に答える
29

すでに他の人によって十分に説明されています。関連する Java 仕様セクションへのリンクを含めるだけです。

x = x++ は式です。Java は評価順序に従います。最初に式 x++ を評価します。これにより、x がインクリメントされ、結果の値が x の前の値に設定されます。次に、式の結果を変数 x に代入します。最後に、x は以前の値に戻ります。

于 2010-09-30T15:33:21.267 に答える
18

この文:

x = x++;

次のように評価します。

  1. xスタックにプッシュします。
  2. インクリメントx;
  3. スタックからポップxします。

したがって、値は変更されません。それを比較してください:

x = ++x;

次のように評価されます。

  1. インクリメントx;
  2. xスタックにプッシュします。
  3. スタックからポップxします。

あなたが望むものは:

while (x < 3) {
  x++;
  System.out.println(x);
}
于 2010-09-30T14:10:20.810 に答える
10

答えは簡単です。物事が評価される順序と関係があります。x++は値を返し、xインクリメントしますx

したがって、式の値x++は です0。したがってx=0、ループ内で毎回割り当てています。確かx++にこの値は増加しますが、それは割り当ての前に発生します。

于 2010-10-01T22:18:17.660 に答える
8

http://download.oracle.com/javase/tutorial/java/nutsandbolts/op1.htmlから

インクリメント/デクリメント演算子は、オペランドの前(プレフィックス)または後(ポストフィックス)に適用できます。コード結果++; および++result; 両方とも結果が1つインクリメントされます。唯一の違いは、プレフィックスバージョン(++ result)が増分値に評価されるのに対し、postfixバージョン(result ++)は元の値に評価されることです。単純なインクリメント/デクリメントを実行しているだけの場合は、どのバージョンを選択してもかまいません。ただし、この演算子をより大きな式の一部で使用する場合は、選択した演算子によって大きな違いが生じる可能性があります。

説明のために、次のことを試してください。

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

1と0を出力します。

于 2010-09-30T14:27:30.653 に答える
8

何が起こっているのかを理解するためにマシンコードは必要ありません。

定義によると:

  1. 代入演算子は右側の式を評価し、一時変数に格納します。

    1.1。x の現在の値がこの一時変数にコピーされます

    1.2. x がインクリメントされます。

  2. 次に、一時変数が式の左側にコピーされます。これはたまたま x です。そのため、 x の古い値が再びそれ自体にコピーされます。

とてもシンプルです。

于 2010-10-01T04:10:05.853 に答える
7

次の動作を効果的に取得しています。

  1. 右辺の「結果」として x (0) の値を取得します
  2. x の値をインクリメントします (つまり x は 1 になります)
  3. 右辺 (0 として保存された) の結果を x に割り当てます (x は現在 0 です)

ポストインクリメント演算子 (x++) は、使用されている方程式で使用するために値を返した後に、問題の変数をインクリメントするという考えです。

編集:コメントのために少し追加します。以下のように考えてください。

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.
于 2010-09-30T14:16:51.490 に答える
5

これは、この場合インクリメントされないためです。x++この場合のようにインクリメントする前に最初にその値を使用します。

x = 0;

でもやれば++x;増えます。

于 2010-09-30T14:11:35.433 に答える
3

の値が 0 なので、値x++は 0 のままです。この場合、 の値xが増加しても増加しなくても代入x=0は実行されます。xこれにより、一時的に増分された値(「非常に短い時間」で 1 だった)が上書きされます。

于 2010-09-30T14:15:55.877 に答える
1

私が見る限り、インクリメント前の値でインクリメントされた値をオーバーライドする割り当てが原因でエラーが発生します。つまり、インクリメントが元に戻されます。

具体的には、「x ++」式は、インクリメント後の「x」の値を持つ「++ x」とは対照的に、インクリメント前の「x」の値を持ちます。

バイトコードの調査に興味がある場合は、問題の3行を調べます。

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

7:iload_1#2番目のローカル変数の値をスタックに配置します
8:iinc 1,1#2番目のローカル変数を1でインクリメントします。これにより、スタックは変更されないことに注意してください。
9:istore_1#スタックの最上位をポップし、この要素の値を2番目のローカル変数に保存します(各JVM命令の効果はここ
で読み取ることができます)

これが、上記のコードが無期限にループするのに対し、++xのバージョンはループしない理由です。++ xのバイトコードはかなり異なって見えるはずです。1年ちょっと前に書いた1.3Javaコンパイラから覚えている限り、バイトコードは次のようになります。

iinc 1,1
iload_1
istore_1

したがって、最初の2行を入れ替えるだけで、セマンティクスが変更され、インクリメント後のスタックの一番上に残っている値(つまり、式の「値」)がインクリメント後の値になります。

于 2010-10-02T01:16:25.080 に答える
1

++ が rhs にある場合、数値がインクリメントされる前に結果が返されます。++x に変更すれば問題ありませんでした。Java では、インクリメントではなく単一の操作 (x から x への代入) を実行するようにこれを最適化します。

于 2010-09-30T23:15:11.640 に答える
1

これは、他のものに期待する方法で機能します。接頭辞と接尾辞の違いです。

int x = 0; 
while (x < 3)    x = (++x);
于 2010-09-30T14:39:12.127 に答える
1
    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;行自体と同じです。

于 2011-11-26T11:08:10.737 に答える
1

x++ は、インクリメント前のX を「返す」関数呼び出しと考えてください(これがポストインクリメントと呼ばれる理由です)。

1: インクリメントする
前に x の値をキャッシュする
2: x をインクリメント
する 3: キャッシュされた値 (インクリメントする前の x)
を返す 4: 戻り値を x に代入する

于 2010-09-30T14:16:20.797 に答える
0

投稿がインクリメントされているために発生しています。これは、式が評価された後に変数がインクリメントされることを意味します。

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

xは10になりましたが、yは9で、インクリメントされる前のxの値です。

詳細については、ポストインクリメントの定義を参照してください。

于 2010-10-01T17:23:40.380 に答える
0

値を 1 ずつインクリメントする前に、値が変数に割り当てられます。

于 2010-10-01T07:59:23.980 に答える
0

Java 仕様に、この動作を正確に定義するものがあるのではないかと思います。(その声明の明らかな意味は、私が怠惰すぎてチェックできないということです。)

Tom のバイトコードから、重要な行は 7、8、および 11 であることに注意してください。行 7 は x を計算スタックにロードします。行 8 は x をインクリメントします。11 行目は、スタックから x に値を格納します。値を自分自身に割り当てない通常のケースでは、ロード、ストア、インクリメントができない理由はないと思います。同じ結果が得られます。

たとえば、次のように記述した、より通常のケースがあるとします。 z=(x++)+(y++);

それが言ったかどうか(専門性をスキップするための擬似コード)

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

無関係であるべきです。どちらの実装も有効である必要があると思います。

この動作に依存するコードを書くことについては、非常に慎重です。私には、実装に非常に依存しているように見えます。違いが生じるのは、ここの例のように異常なことをした場合、または 2 つのスレッドを実行していて、式内の評価の順序に依存している場合だけです。

于 2010-09-30T19:55:18.667 に答える
0

式はx++に評価されxます。この部分は、ステートメントの後ではなく、評価++後の値に影響します。そう効果的に翻訳されますx = x++

int y = x; // evaluation
x = x + 1; // increment part
x = y; // assignment
于 2010-10-01T16:38:27.583 に答える
0
 x = x++; (increment is overriden by = )

上記のステートメントにより、x が 3 に達することはありません。

于 2010-09-30T17:30:43.547 に答える
0

Java ++では=(割り当て)よりも優先順位が高いためだと思います...そうですか?http://www.cs.uwf.edu/~eelsheik/cop2253/resources/op_precedence.htmlを見てください...

x=x+1...+ と書くと、同じように = (代入) よりも優先順位が高くなります。

于 2010-10-01T07:00:54.023 に答える
-1

インクリメント演算子は、割り当て先と同じ変数に適用されます。それはトラブルを求めています。このプログラムの実行中に x 変数の値を確認できると確信しています....これで、ループが終わらない理由が明確になるはずです。

于 2010-12-10T11:29:09.037 に答える