なぜx ==(x = y)は(x = y)== xと同じではないのですか?


207

次の例を検討してください。

class Quirky {
    public static void main(String[] args) {
        int x = 1;
        int y = 3;

        System.out.println(x == (x = y)); // false
        x = 1; // reset
        System.out.println((x = y) == x); // true
     }
}

Java言語仕様に、右側(x = y)と比較するために変数の前の値をロードすることを指示する項目があるかどうかはわかりません。

なぜ最初の式はにfalse評価されtrueますが、2番目の式はに評価されますか?私は(x = y)最初に評価されることを期待し、それからそれx自体と比較して(3)戻りtrueます。


この質問は異なっているJava式で部分式の評価順序という点で、xここでは間違いなく「部分式」ではありません。「評価」するのではなく、比較のためにロードする必要があります。質問はJava固有であり、表現はx == (x = y)、一般的に巧妙なインタビューの質問のために作成された、遠くにある非実用的な構成とは異なり、実際のプロジェクトからのものです。これは、比較および置換イディオムの1行の置換であると想定されていました

int oldX = x;
x = y;
return oldX == y;

これは、x86 CMPXCHG命令よりもさらに単純であるため、Javaでは短い式に値します。


62
左側は常に右側より先に評価されます。括弧はそれを変えません。
Louis Wasserman

11
式の評価x = yは確かに関連性がありx、の値に設定される副作用を引き起こしますy
Louis Wasserman

50
自分自身とチームメイトに好意を示し、状態検査と同じ行に状態変化を混在させないでください。そうすると、コードの可読性が大幅に低下します。(原子性の要件のために絶対に必要な場合もありますが、それらの機能はすでに存在しており、その目的は即座に認識されます。)
jpmc26

50
本当の問題は、なぜこのようなコードを書きたいのかということです。
klutt 2018

26
あなたの質問の鍵は、括弧が評価の順序を意味するというあなたの誤った信念です。小学校で数学をどのように教えたのか、また初心者向けプログラミングの本ではまだ間違っているので、それは一般的な考えですが、それは誤った考えです。これはかなりよくある質問です。あなたは私の主題に関する私の記事を読むことから利益を得るかもしれません。それらはC#についてですが、Javaに適用されます:ericlippert.com/2008/05/23/precedence-vs-associativity-vs-order ericlippert.com/2009/08/10/precedence-vs-order-redux
Eric Lippert

回答:


97

大括弧で示される順序で、最初に計算する必要があります

いいえ。括弧が計算または評価の順序に(一般的な)影響を与えることはよくある誤解です。これらは、式の一部を特定のツリーに強制変換するだけであり、適切なオペランドをジョブの適切な演算にバインドします。

(そして、それらを使用しない場合、この情報は、演算子の「優先順位」と結合性から得られます。これは、言語の構文ツリーが定義された方法の結果であるものです。実際、これは、かっこを使用しますが、簡略化して、その場合は優先ルールに依存しないと言います。)

それが終わっても(つまり、コードがプログラムに解析されても)それらのオペランドはまだ評価される必要があり、その方法については別のルールがあります:上記のルール(Andrewが示したように)には、各操作のLHSが記載されています最初にJavaで評価されます。

これはすべての言語に当てはまるわけではないことに注意してください。たとえば、C ++では、&&またはのような短絡演算子を使用している場合を除き||、オペランドの評価順序は一般に指定されておらず、どちらの方法にも依存しないでください。

教師は、「これにより追加が最初に行われる」などの誤解を招く表現を使用して、演算子の優先順位を説明するのをやめる必要があります。式x * y + zが与えられた場合、適切な説明は、「演算子の優先順位により、「と」の間x * yzはなくyz「と」の間で加算が行われ、「順序」についての言及がない場合です。


6
先生が、基礎となる数学とそれを表すために使用する構文をある程度分離させてほしいと思います。たとえば、ローマ数字やポーランド記法などで1日過ごし、その加算に同じ特性があることを確認したような場合です。私たちは連想性とそれらすべての特性を中学校で学びましたので、十分な時間がありました。
ジョンP

1
このルールがすべての言語に当てはまるわけではないことを述べました。また、どちらの側にも、ファイルへの書き込みや現在時刻の読み取りなどの別の副作用がある場合、(Javaでも)発生する順序は定義されていません。ただし、比較の結果は、Javaで左から右に評価されたかのようになります。別の問題は別として、かなりの数の言語は、構文規則によるこの方法での割り当てと比較の混合を単に許可しておらず、問題は発生しません。
Abel、

5
@JohnP:悪化します。5 * 4は5 + 5 + 5 + 5または4 + 4 + 4 + 4 + 4を意味しますか?一部の教師は、これらの選択肢の1つだけが正しいと主張します。
ブライアン、

3
@ブライアンしかし...しかし...実数の乗算は可換です!
18:18のオービット

2
私の考える世界では、一対の括弧は「必要」を表します。´a *(b + c) ´を計算すると、括弧は加算の結果が乗算に必要であることを表します。LHSファーストまたはRHSファーストのルールを除き、暗黙のオペレーター設定はすべて括弧で表すことができます。(それは本当ですか?)@ブライアン数学では、乗算を繰り返し加算で置き換えることができるまれなケースがいくつかありますが、それは必ずしも真ではありません(最初は複素数で始まりますが、それに限定されません)。だからあなたの教育者は本当に人々が何を言っているかに目を向けるべきです...
syck

164

==は、バイナリ演算子です。

左側のオペランド二項演算子のは十分に評価しているように見えるの前の任意の部分右側のオペランドが評価されます。

Java 11仕様>評価順序>左側のオペランドを最初に評価


42
「あるように見える」という言葉は、彼らが確信しているようには聞こえません、tbh。
リスター氏

86
「あるように見える」とは、仕様が実際に時系列で操作が実際に実行されることを要求していないことを意味しますが、実際と同じ結果を得る必要があります。
Robyn 2018

24
@MrListerは「存在しているように見える」というのは、彼らにとって不適切な単語の選択であるように見えます。「現れる」とは、「開発者にとって現象として現れる」ことを意味します。「効果的」はより適切な語句かもしれません。
ケルビン

18
C ++コミュニティでは、これは「as-if」ルールと同等です...技術的にそうではない場合でも、オペランドは、次のルールに従って実装された「あたかも」動作する必要があります。
Michael Edenfield 2018

2
@ケルビン同意する、「そのように見える」というよりはむしろその言葉を選んだのだろう。
MC皇帝

149

LouisWassermanが言ったように、式は左から右に評価されます。そして、Javaは「評価」が実際に何をするかを気にせず、操作する(非揮発性の、最終的な)値を生成することだけを気にします。

//the example values
x = 1;
y = 3;

したがって、の最初の出力を計算するにSystem.out.println()は、次のようにします。

x == (x = y)
1 == (x = y)
1 == (x = 3) //assign 3 to x, returns 3
1 == 3
false

そして秒を計算するには:

(x = y) == x
(x = 3) == x //assign 3 to x, returns 3
3 == x
3 == 3
true

第二の値は、常にの初期値に関係なく、trueと評価されることに注意してくださいxy、あなたは効果的にそれが割り当てられている変数に値の代入を比較しているため、とa = bしてb、その順序で評価され、常に同じであること定義により。


数学では「左から右」も当てはまります。ちなみに、括弧または優先順位に達したときに、括弧内を反復処理し、左から右にすべてを評価してから、メイン階層に進みます。しかし、数学はこれを行うことはありません。これは方程式ではなくコンボ演算であり、割り当てと方程式の両方を一度に実行するため、区別が重要になります。コードゴルフをしている場合や、パフォーマンスを最適化する方法を探している場合や、コメントがある場合を除き、読みやすさが悪いため、これを行うことはありません。
ハーパー-モニカを復活させる

25

変数の以前の値のロードを指示するJava言語仕様の項目があるかどうかはわかりません...

有る。あなたは仕様が言う不明である次回は、仕様書を読んでください。そして、それが不明である場合は質問します。

... (x = y)括弧によって暗示される順序で、最初に計算する必要がある右側。

その声明は誤りです。括弧は評価の順序を意味しません。Javaでは、括弧に関係なく、評価の順序は左から右です。括弧は、評価の順序ではなく、部分式の境界がどこにあるかを決定します。

最初の式がfalseと評価され、2番目の式がtrueと評価されるのはなぜですか?

==演算子のルールは、左側を評価して値を生成し、右側を評価して値を生成し、値を比較します。比較は式の値です。

つまり、の意味は、expr1 == expr2作成temp1 = expr1; temp2 = expr2;して評価した場合と常に同じですtemp1 == temp2

=左側にローカル変数がある演算子のルールは、左側を評価して変数を生成し、右側を評価して値を生成し、割り当てを実行します。結果は割り当てられた値です。

まとめてください:

x == (x = y)

比較演算子があります。左側を評価して値を生成します-の現在の値を取得しますx。右側を評価します。これは代入なので、左側を評価して変数(変数)を生成します。変数- x右側を評価します-現在の値y-にx割り当てます。結果は割り当てられた値です。次に、の元の値xを、割り当てられた値と比較します。

(x = y) == x演習として行うことができます。繰り返しますが、左側を評価するためのすべてのルールは、右側を評価するすべてのルールの前に発生します。

(x = y)が最初に評価されることを期待し、次にxをそれ自体と比較して(3)、trueを返します。

あなたの期待は、Javaのルールについての誤った信念のセットに基づいています。うまくいけば、今あなたは正しい信念を持っており、将来的には本当のことを期待するでしょう。

この質問は、「Java式の部分式の評価順序」とは異なります。

このステートメントは誤りです。その質問は完全に密接に関係しています。

ここではxは間違いなく「部分式」ではありません。

このステートメントも偽です。各例で2回の部分式です。

「評価」するのではなく、比較のためにロードする必要があります。

これが何を意味するのか私にはわかりません。

どうやらあなたはまだ多くの誤った信念を持っています。私のアドバイスは、あなたの誤った信念が真の信念に置き換わるまで仕様を読むことです。

質問はJava固有であり、x ==(x = y)という表現は、トリッキーなインタビューの質問のために一般的に作成された、遠くから得られる非実用的な構成とは異なり、実際のプロジェクトからのものです。

表現の由来は質問には関係ありません。そのような表現の規則は、仕様で明確に説明されています。それを読んで!

これは、比較と置換のイディオムの1行の置き換えになるはずでした。

この1行の置換は、コードの読者であるあなたに多大な混乱を引き起こしたので、それは不適切な選択だったと思います。コードをより簡潔にすることはできますが、理解するのは難しくなりますが、勝ちではありません。コードを高速化することはほとんどありません。

ちなみに、C#には比較と置換がライブラリメソッドとして用意されており、機械語命令に変換できます。Javaの型システムでは表現できないため、Javaにはそのようなメソッドがないと思います。


8
誰かがJLS全体を通過できれば、Javaの本を出版する理由はなく、このサイトの少なくとも半分も役に立たないでしょう。
ジョンマクレイン2018

8
@JohnMcClane:私はあなたに保証します、仕様全体を通過することは何の困難もありませんが、あなたがする必要はありません。Java仕様は、最も関心のある部分にすばやくアクセスするのに役立つ便利な「目次」から始まります。オンラインおよびキーワード検索も可能です。そうは言っても、あなたは正しいです。Javaがどのように機能するかを学ぶのに役立つ多くの優れたリソースがあります。あなたへの私のアドバイスは、あなたがそれらを利用することです!
エリックリッペルト2018

8
この答えは不必要に無礼で失礼です。注意:いいです
walen

7
@LuisG .:軽蔑を意図したり暗示したりすることはありません。私たちは皆、お互いに学び合うためにここにいますし、私が初心者だったときに自分でやったことがないことは何もお勧めしません。それも失礼ではありません。彼らの誤った信念を明確かつ明確に特定することは、元のポスターに対する親切です。「礼儀正しさ」の背後に隠れて人々が誤った信念を持ち続けることを許可することは役に立たず思考の悪い習慣を強化します。
Eric Lippert

5
@LuisG .:私はJavaScriptのデザインについてブログを書いていましたが、私がこれまでに得た最も役立つコメントは、ブレンダンからはっきりと明確に指摘し、どこが間違っているのかを指摘してくれました。それは素晴らしかったし、彼が時間を割いてくれたことに感謝しました。それから私は自分の仕事でその間違いを繰り返さずに、またはさらに悪いことに他の人にそれを教えることなく、人生の次の20年間を生きたからです。それはまた、人々が偽りの事柄を信じるようになる方法の例として自分自身を使用することにより、他の人たちの同じ誤った信念を修正する機会を私に与えました。
Eric Lippert

16

これは、演算子の優先順位と演算子の評価方法に関連しています。

括弧 '()'は優先順位が高く、関連付けは左から右です。この質問の次の式は「==」であり、左から右に連想性があります。割り当て '='は最後に来て、右から左に関連性があります。

システムはスタックを使用して式を評価します。式は左から右に評価されます。

今、元の質問になります:

int x = 1;
int y = 3;
System.out.println(x == (x = y)); // false

最初にx(1)がスタックにプッシュされます。次に、内部(x = y)が評価され、値x(3)でスタックにプッシュされます。ここで、x(1)はx(3)と比較されるため、結果はfalseになります。

x = 1; // reset
System.out.println((x = y) == x); // true

ここでは、(x = y)が評価され、xの値が3になり、x(3)がスタックにプッシュされます。これで、等価後に値が変更されたx(3)がスタックにプッシュされます。これで式が評価され、両方が同じになるため、結果はtrueになります。


12

同じではありません。左側は常に右側より先に評価され、括弧は実行順序を指定するのではなく、コマンドのグループを指定します。

と:

      x == (x = y)

あなたは基本的に同じことをしています:

      x == y

そして、xは比較後にyの値を持ちます。

と一緒に:

      (x = y) == x

あなたは基本的に同じことをしています:

      x == x

xyの値を取った 後。そして、常にtrueを返します


9

チェックしている最初のテストでは、1 == 3を実行します。

2番目のテストでは、チェックは3 == 3です。

(x = y)は値を割り当て、その値がテストされます。前者の例では、最初にx = 1、次にxが3に割り当てられます。1== 3ですか?

後者の場合、xには3が割り当てられ、明らかに3のままです。3== 3ですか?


8

この他の、おそらくもっと簡単な例を考えてみましょう:

int x = 1;
System.out.println(x == ++x); // false
x = 1; // reset
System.out.println(++x == x); // true

ここでは、比較が行わ++xれるにプリインクリメント演算子を適用する必要があります— (x = y)例のよう、比較に計算する必要があります。

ただし、式の評価は依然として左→右→に行われるため、最初の比較は実際1 == 2には2番目の比較です2 == 2
あなたの例でも同じことが起こります。


8

式は左から右に評価されます。この場合:

int x = 1;
int y = 3;

x == (x = y)) // false
x ==    t

- left x = 1
- let t = (x = y) => x = 3
- x == (x = y)
  x == t
  1 == 3 //false

(x = y) == x); // true
   t    == x

- left (x = y) => x = 3
           t    =      3 
-  (x = y) == x
-     t    == x
-     3    == 3 //true

5

基本的に最初のステートメントxの値は1だったので、Javaは1 ==を同じではない新しいx変数と比較します

2番目のものでは、x = yと言いました。これは、xの値が変更されたことを意味します。そのため、もう一度呼び出すと、同じ値になるため、trueでx == xになります。


4

==は比較等価演算子であり、左から右に機能します。

x == (x = y);

ここで、xの古い割り当て値がxの新しい割り当て値と比較されます(1 == 3)// false

(x = y) == x;

一方、xの新しい割り当て値は、比較の直前に割り当てられたxの新しい保持値と比較されます(3 == 3)// true

今これを考えて

    System.out.println((8 + (5 * 6)) * 9);
    System.out.println(8 + (5 * 6) * 9);
    System.out.println((8 + 5) * 6 * 9);
    System.out.println((8 + (5) * 6) * 9);
    System.out.println(8 + 5 * 6 * 9);

出力:

342

278

702

342

278

したがって、括弧は、比較式ではなく、算術式で主要な役割を果たします。


1
結論は間違っています。動作は、算術演算子と比較演算子で違いはありません。x + (x = y)また(x = y) + x、比較演算子を使用した元の動作と同様の動作を示します。
JJJ 2018

1
@JJJ x +(x = y)および(x = y)+ xでは、関係する比較は行われず、xにy値を割り当て、xに追加するだけです。
Nisrin Dhoondia

1
...はい、それがポイントです。算術式と比較式に違いがないため、「括弧は比較式でなく、算術式でのみ主要な役割を果たします」は誤りです。
JJJ 2018

2

ここでの事は2つの事業者のarithmatic事業者/関係演算子の先番順序出ている===支配的である==ことが先行して(関係演算子が支配的)=代入演算子を。優先順位にもかかわらず、評価の順序はLTR(左から右)です。優先順位は、評価順序の後に現れます。したがって、制約の評価に関係なく、LTRになります。


1
答えは間違っています。演算子の優先順位は、評価順序には影響しません。よく投票された回答のいくつかを読んで、特にこれについて説明します。
JJJ 2018

1
正しい、我々が優先の制限のような錯覚を教えているその実際の方法は、n個のすべてのそれらのものが来るが、正しくは左から右への評価遺跡の順序だって全く影響がありません指摘
ヒマンシュアフジャ

-1

左側の2番目の比較は、yをx(左側)に割り当てた後の代入で簡単です。次に、3 == 3を比較します。最初の例では、x = 1と新しい代入x = 3を比較しています。 xの左から右に向かって常に現在の状態を読み取るステートメントが取られます。


-2

Javaコンパイラーを作成する場合、またはJavaコンパイラーが正常に動作していることを確認するためのプログラムをテストする場合、あなたが尋ねた種類の質問は非常に良い質問です。Javaでは、これらの2つの式は、見た結果を生成する必要があります。たとえばC ++では、その必要はありません。つまり、誰かがC ++コンパイラの一部をJavaコンパイラで再利用した場合、理論的にはコンパイラが正しく動作しないことがあります。

ソフトウェア開発者として、読みやすく、理解しやすく、保守可能なコードを作成する場合、コードの両方のバージョンはひどいと見なされます。コードの機能を理解するには、Java言語がどのように定義されているかを正確に知る必要があります。JavaとC ++の両方のコードを書く人は、コードを見ると身震いするでしょう。1行のコードがなぜそれを実行するのかを尋ねる必要がある場合は、そのコードを回避する必要があります。(私はあなたの「なぜ」の質問に正しく答えた人たちも彼ら自身もそのコードのインドを避けてくれると思い、期待しています)。


「コードの機能を理解するには、Java言語がどのように定義されているかを正確に知る必要があります。」しかし、すべての同僚がそれを常識だとしたらどうでしょうか。
BinaryTreeee
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.