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? 나는 (x = y)먼저 평가 될 것으로 예상 했을 때 x자체와 비교 하고 ( 3) return true.


이 질문은 Java 표현식 에서 하위 표현식의 평가 순서와 다르며 x여기서는 분명히 '하위 표현식'이 아닙니다. '평가'하기보다는 비교 를 위해 로드 되어야합니다. x == (x = y)까다로운 인터뷰 질문을 위해 제작 된 비현실적인 구성과 달리 실제 프로젝트에서 나온 표현이 Java에 고유합니다 . 비교 및 교체 관용구를 한 줄로 대체해야했습니다.

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

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 ++에서 &&or 와 같은 단락 연산자를 사용하지 않는 한 ||피연산자의 평가 순서는 일반적으로 지정되어 있지 않으며 어떤 식 으로든 의존해서는 안됩니다.

교사는 "이렇게하면 추가가 먼저 이루어집니다"와 같은 잘못된 문구를 사용하여 운영자 우선 순위 설명을 중단해야합니다. 식을 감안할 때 x * y + z적절한 설명은 "연산자 우선 순위는 또한 사이에 발생하게 될 것 x * yz오히려 사이보다, y그리고 z어떤"순서 "의 언급은 없다".


6
로마 숫자 나 폴란드어 표기법으로 하루를 보내거나 추가가 동일한 속성을 갖는 것을 보았을 때와 같이 선생님이 기본 수학과 수학을 표현할 때 사용한 구문을 약간 분리했으면합니다. 우리는 중학교에서 연관성과 모든 속성을 배웠으므로 시간이 많이있었습니다.
John P

1
이 규칙이 모든 언어에 적용되는 것은 아닙니다. 또한 파일에 쓰거나 현재 시간을 읽는 것과 같이 어느 한쪽에 다른 부작용이있는 경우, 발생 순서에 따라 (Java에서도) 정의되지 않습니다. 그러나 비교 결과는 왼쪽에서 오른쪽으로 평가 된 것처럼 나타납니다 (Java에서). 또 다른 측면 : 꽤 많은 언어가 구문 규칙에 의한 할당과 비교를 허용하지 않으므로 문제가 발생하지 않습니다.
Abel

5
@JohnP : 더 나빠집니다. 5 * 4는 5 + 5 + 5 + 5 또는 4 + 4 + 4 + 4 + 4를 의미합니까? 일부 교사는 그러한 선택 중 하나만 옳다고 주장합니다.
Brian

3
@Brian 그러나 ...하지만 ... 실수의 곱셈은 정식입니다!
궤도에서 가벼움 레이스

2
내 생각의 세계에서, 한 쌍의 괄호는 "필요하다"를 나타낸다. "a * (b + c)"를 계산할 때, 괄호는 곱셈에 덧셈 의 결과 가 필요하다는 것을 나타냅니다 . LHS-first 또는 RHS-first 규칙을 제외한 모든 암시 적 운영자 기본 설정은 parens로 표현할 수 있습니다 . @Brian 수학에서 곱셈을 반복 덧셈으로 대체 할 수있는 드문 경우가 몇 가지 있지만 항상 사실이 아닙니다 (복소수로 시작하지만 이에 국한되지는 않음). 그래서 당신의 교육자들은 사람들이 무엇을 말하는지에 대해 정말로주의를 기울여야합니다 ....
syck

164

==이진 항등 연산자 입니다.

이항 연산자 의 왼쪽 피연산자오른쪽 피연산자의 일부가 평가 되기 전에 완전히 평가 된 것으로 보입니다 .

Java 11 사양> 평가 순서> 먼저 왼쪽 피연산자 평가


42
"있는 것처럼 보인다"는 말은 확실하지 않습니다.
Mr Lister

86
"있는 것처럼 보인다"는 사양에서 작업이 실제로 순서대로 순서대로 수행 될 것을 요구하지는 않지만, 원래의 결과와 동일한 결과를 얻을 것을 요구합니다.
로빈

24
@MrLister "appears to be"는 그 자체로 잘못된 단어 선택 인 것으로 보입니다. "나타난다"는 "개발자에게 현상으로서의 매니페스트"를 의미합니다. "효과적으로"는 더 나은 문구 일 수 있습니다.
켈빈

18
C ++ 커뮤니티에서 이것은 "as-if"규칙과 같습니다. 피연산자는 기술적으로 그렇지 않더라도 다음 규칙에 따라 구현 된 것처럼 "있는 것처럼"동작해야합니다.
마이클 에덴 필드

2
@Kelvin 동의합니다. "있는 것처럼 보이기보다는"라는 단어를 선택했을 것입니다.
MC 황제

149

LouisWasserman이 말했듯이 표현은 왼쪽에서 오른쪽으로 평가됩니다. 그리고 자바는 "평가"가 실제로하는 일에 신경 쓰지 않으며, (비 휘발성, 최종) 값을 생성하는 것에 만 관심이있다.

//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로 평가되므로주의 x하고 y, 효과적으로이 할당 된 변수에 값 할당을 비교하기 때문에, 그리고 a = b하고 b, 그 순서대로 평가됩니다 항상 동일 정의에 의해.


수학적으로 "왼쪽에서 오른쪽으로"는 괄호 나 우선 순위에 도달하면 내부에서 반복하고 왼쪽에서 오른쪽으로 모든 것을 평가하여 주 계층으로 넘어갑니다. 그러나 수학은 결코 이것을하지 않을 것입니다. 이 식은 방정식이 아니라 콤보 연산이므로 할당과 방정식을 모두 한 번에 수행 하기 때문에 차이가 중요 합니다. 코드 골프를하거나 성능을 최적화 할 방법을 찾고 있지 않으면 가독성이 좋지 않기 때문에이 작업을 수행하지 않을 것입니다.
하퍼-복원 모니카

25

Java 언어 사양에 변수의 이전 값을로드하도록 지시하는 항목이 있는지 확실하지 않습니다 ...

있습니다. 당신이 사양의 말씀 불분명 다음 번에, 사양을 읽어 주시기 바랍니다 다음 이 명확하지 않은 경우 질문.

... 오른쪽 (x = y)대괄호로 암시 된 순서로 먼저 계산해야합니다.

그 진술은 거짓이다. 괄호는 평가 순서를 의미하지 않습니다 . Java에서 괄호에 관계없이 평가 순서는 왼쪽에서 오른쪽입니다. 괄호는 하위 식 경계가 평가 순서가 아닌 위치를 결정합니다.

첫 번째 표현은 왜 거짓으로 평가되고 두 ​​번째 표현은 참으로 평가됩니까?

에 대한 규칙 ==연산자 은 다음과 같습니다. 왼쪽을 평가하여 값을 생성하고, 오른쪽을 평가하여 값을 생성하고 값을 비교하면 비교는 표현식의 값입니다.

다시 말해, 의미 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는 분명히 '하위 표현'이 아닙니다.

이 진술은 또한 거짓입니다. 각 예에서 번의 하위 표현식 입니다.

'평가'하기보다는 비교를 위해로드되어야합니다.

나는 이것이 무엇을 의미하는지 전혀 모른다.

분명히 당신은 여전히 ​​많은 거짓 신념을 가지고 있습니다. 나의 충고는 당신의 거짓 신념이 진정한 신념으로 대체 될 때까지 사양을 읽는 것입니다.

이 질문은 자바에 특화되어 있으며 까다로운 인터뷰 질문을 위해 일반적으로 제작 된 파렴치한 비현실적인 구성과 달리 표현 x == (x = y)는 실제 프로젝트에서 나왔습니다.

표현의 출처는 질문과 관련이 없습니다. 이러한 표현에 대한 규칙은 명세서에 명확하게 설명되어 있습니다. 읽어!

비교 및 교체 관용구를 한 줄로 대체해야했습니다.

한 줄로 교체하면 코드를 읽는 독자에게 많은 혼란이 생겼으므로 선택이 잘못되었다고 제안합니다. 코드를 더 간결하지만 이해하기 어렵게 만드는 것은 승리가 아닙니다. 코드를 더 빨리 만들지 않을 것입니다.

또한, C 번호가있다 비교 대체 라이브러리 방법으로 할 수 있는 기계 명령어까지 jitted한다. Java 유형 시스템에서는 표현할 수 없으므로 Java에는 그러한 메소드가 없다고 생각합니다.


8
누구나 JLS 전체를 살펴볼 수 있다면 Java 서적을 출판 할 이유가 없으며이 사이트의 적어도 절반도 쓸모가 없습니다.
John McClane

8
@JohnMcClane : 전체 사양을 처리하는 데 어려움이 없지만 반드시 그럴 필요는 없습니다. Java 사양은 도움이되는 "목차"로 시작하여 가장 관심있는 부분에 빠르게 접근 할 수 있습니다. 또한 온라인 및 키워드 검색도 가능합니다. 즉, 당신은 옳습니다. Java의 작동 방식을 배우는 데 도움이되는 좋은 자료가 많이 있습니다. 당신에게 나의 충고는 당신이 그들을 이용한다는 것입니다!
Eric Lippert

8
이 답변은 불필요하게 상식적이고 무례합니다. 기억하십시오 : 멋지십시오 .
walen

7
@LuisG .: 어떠한 경시도 의도되거나 암시되지 않습니다. 우리는 모두 서로에게서 배우기 위해 여기에 있습니다. 저는 초보자 일 때하지 않은 것을 추천하지 않습니다. 무례하지도 않습니다. 그들의 잘못된 믿음을 분명하고 분명하게 식별하는 것은 원래 포스터에 대한 친절 입니다. "정치"를 숨기고 사람들이 계속 거짓 신념을 가질 수있게하는 것은 도움되지 않으며 나쁜 사고 습관을 강화 시킵니다.
Eric Lippert

5
@LuisG .: JavaScript 디자인에 관한 블로그를 작성 했었고, 내가 얻은 가장 유용한 의견은 Brendan이 내가 잘못한 부분을 명확하고 분명하게 지적한 것입니다. 나는 그 시간을내어 감사했습니다. 나는 그 후 20 년 동안 내 자신의 일에서 그 실수를 반복하지 않고 다른 사람들에게 그 잘못을 가르치지 않았기 때문에 내 인생을 살았 기 때문에 감사했습니다. 또한 사람들이 어떻게 거짓을 믿게되는지에 대한 모범으로 자신을 사용함으로써 다른 사람들에 대한 동일한 거짓 신념을 바로 잡을 수있는 기회를주었습니다.
Eric Lippert

16

연산자 우선 순위 및 연산자 평가 방법과 관련이 있습니다.

괄호 '()'가 우선 순위가 높고 연관성이 왼쪽에서 오른쪽입니다. 이 질문의 다음에 평등 '=='이 나오고 왼쪽에서 오른쪽으로 연관성이 있습니다. 할당 '='이 마지막에 오며 오른쪽에서 왼쪽으로 연관성이 있습니다.

시스템 사용 스택은 식을 평가합니다. 식은 왼쪽에서 오른쪽으로 평가됩니다.

이제 원래 질문에 온다 :

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

첫 x (1)은 스택으로 푸시됩니다. 그러면 inner (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)이 스택으로 푸시됩니다. 이제 표현이 평가되고 둘 다 동일하므로 결과가 참입니다.


12

동일하지 않습니다. 왼쪽은 항상 오른쪽보다 먼저 평가되며 괄호는 실행 순서를 지정하지 않고 명령 그룹을 지정합니다.

와:

      x == (x = y)

기본적으로 다음과 같은 작업을 수행합니다.

      x == y

그리고 x 는 비교 후 y 값을 갖습니다 .

함께하는 동안 :

      (x = y) == x

기본적으로 다음과 같은 작업을 수행합니다.

      x == x

xy 값을 취한 후 그리고 항상 true를 반환 합니다 .


9

첫 번째 테스트에서 1 == 3을 수행합니다.

두 번째 테스트에서 검사는 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 입니다.
귀하의 예에서도 마찬가지입니다.


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 변수와 비교합니다.

두 번째 것에서는 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

1
@JJJ x + (x = y) 및 (x = y) + x에서는 비교가 필요하지 않습니다. x에 y 값을 할당하고 x에 추가하는 것입니다.
Nisrin Dhoondia

1
... 그렇습니다. 요점입니다. "산술 표현 과 비교 표현 사이에는 차이가 없기 때문에 수학은 산술 표현에서만 중요한 역할을 합니다."
JJJ

2

여기에있는 것은 두 연산자 중 산술 연산자 / 관계 연산자 우선 순위 ===지배적 연산자보다 우선 순위 가 높은 연산자 입니다 ==(관계 연산자가 지배합니다) =. 우선 순위에도 불구하고, 평가 순서는 LTR (왼쪽에서 오른쪽)입니다. 우선 순위는 평가 순서 후에 나타납니다. 따라서 제약 조건 평가와 상관없이 LTR입니다.


1
답이 틀렸다. 연산자 우선 순위는 평가 순서에 영향을 미치지 않습니다. 설명은 최상위 투표 답변의 일부, 특히 읽기 이 하나 .
JJJ

1
올바른, 우리는 우선 순위에 대한 제한의 환상을 배운다는 사실 방법은 N 모든 것을 제공하지만, 정확하게는 왼쪽에서 오른쪽으로 평가 유적의 순서 사촌 아무런 영향이 없습니다 지적
Himanshu Ahuja

-1

왼쪽의 두 번째 비교에서 y를 x (왼쪽)에 할당 한 후 3 == 3을 비교 한 후 할당하는 것이 쉽습니다. 첫 번째 예에서는 x = 1을 새로운 assign x = 3과 비교하는 것 같습니다. 항상 x의 왼쪽에서 오른쪽으로 현재 상태 판독문이 사용됩니다.


-2

Java 컴파일러를 작성하거나 Java 컴파일러가 올바르게 작동하는지 확인하기 위해 프로그램을 테스트하려는 경우 요청한 질문은 매우 좋은 질문입니다. Java에서이 두 표현식은 사용자가 본 결과를 생성해야합니다. 예를 들어, C ++에서는 필요하지 않습니다. 누군가 누군가 Java 컴파일러에서 C ++ 컴파일러의 일부를 재사용 한 경우 이론적으로 컴파일러가 정상적으로 작동하지 않는 것을 알 수 있습니다.

읽기 쉽고 이해하기 쉬우 며 유지 관리가 가능한 코드를 작성하는 소프트웨어 개발자는 두 버전의 코드가 모두 끔찍한 것으로 간주됩니다. 코드의 기능을 이해하려면 Java 언어가 어떻게 정의 되어 있는지 정확히 알아야 합니다 . Java와 C ++ 코드를 모두 작성하는 사람은 코드를보고 깜짝 놀랐습니다. 왜 한 줄의 코드가 그 기능을 수행하는지 묻어 야하는 경우 해당 코드를 피해야합니다. (귀하의 "왜"질문에 올바르게 답변 한 사람들도 그 코드를 피할 수 있기를 바랍니다.


"코드의 기능을 이해하려면 Java 언어가 어떻게 정의되어 있는지 정확히 알아야합니다." 그러나 모든 동료가 상식적으로 생각한다면 어떨까요?
BinaryTreeee
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.