java member variable




なぜこれは無限ループに入りますか? (18)

私は次のコードを持っています:

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

}

私は理解しようとするinstructionsについて読む...


  1. 接頭辞表記は、式が評価される前に変数をインクリメントします。
  2. 接尾辞表記は式の評価後にインクリメントします。

しかし、 " = "は " ++ "よりも演算子の優先度が低くなります。

だからx=x++; 次のように評価する必要があります

  1. x割り当ての準備(評価)
  2. xインクリメントした
  3. x割り当てられた以前のx値。

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


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

インクリメント/デクリメント演算子は、オペランドの前(接頭辞)または後(接尾辞)に適用できます。 コード結果++; ++結果; 結果は両方とも1だけインクリメントされます。 唯一の違いは、接頭辞バージョン(++ result)はインクリメントされた値に評価され、後バージョン(result ++)は元の値に評価される点です 。 単純な増分/減分を実行しているだけの場合は、どのバージョンを選択するかは問題になりません。 しかし、より大きな式の一部でこの演算子を使用すると、選択した演算子が大きな違いを生むことがあります。

説明するには、以下を試してください:

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

1と0が出力されます。


x = 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

x++式はx評価されます。 ++部分は評価後の値に影響を与えますが、 ステートメントの後には反映されません。 x = x++は効果的に次のように変換されます。

int y = x; // evaluation
x = x + 1; // increment part
x = y; // assignment

:最初は、説明のためにこの答えにC#コードを掲載しました.C#では、 refキーワードを使用してintパラメータを参照することができます。 私はGoogleで最初に見つけたMutableIntクラスを使って実際の法的Javaコードで更新することに決めました。 私はそれが答えを助けるか、または傷つけるかどうか本当に言うことができません。 私は私が個人的に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に置き換えると、「xを最初に増やし、後でyに代入するのが分かります」と混乱しているという事実を固定します。 yに代入されているxではありません。 以前は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 = 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の両方の不規則な発生に注目してください。

Starting background thread...
0
0
1
1
0
0
0
0
0
0
0
0
0
0
1
0
1

あなたは効果的に次のような行動を取っています。

  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.

この文:

x = x++;

このように評価する:

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

したがって値は変更されません。 それを比較する:

x = ++x;

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

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

あなたが望むものは次のとおりです:

while (x < 3) {
  x++;
  System.out.println(x);
}

これは、あなたが他の人にどのように期待しているかに作用します。 接頭辞と接尾辞の違いです。

int x = 0; 
while (x < 3)    x = (++x);

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

x = 0;

しかし、もしあなたが++x;をしたら++x; これは増加するでしょう。


どこに答えがあるのか​​わからないので、ここに行く:

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の値を読むと、更新された値、つまり1が返されます。


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


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


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

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

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

ポストインクリメントの定義を参照してください。


私が見る限り、エラーはインクリメントされた値をオーバーライドする割り当てのために、インクリメントの前の値、つまりインクリメントを取り消します。

具体的には、「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命令の効果をhereで読むことができhere

このため、上記のコードは無期限にループしますが、++ xのバージョンはループしません。 私が1年以上前に書いた1.3 Javaコンパイラから覚えている限り、バイトコードは次のようになるはずです。

iinc 1,1
iload_1
istore_1

つまり、2つの最初の行をスワップするだけで、インクリメント(つまり、式の 'value')の後にスタックの先頭に残っている値がインクリメント後の値になるようにセマンティクスが変更されます。



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

したがって、式x++値は0です。 したがって、毎回ループにx=0割り当てます。 確かにx++この値をインクリメントしますが、それは代入の前に起こります。


    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++; または++x; それ自身で行に。





increment