Java의 평가 순서에 대한 규칙은 무엇입니까?


86

일부 Java 텍스트를 읽고 다음 코드를 받았습니다.

int[] a = {4,4};
int b = 1;
a[b] = b = 0;

본문에서 저자는 명확한 설명을하지 않았으며 마지막 줄의 효과는 다음과 같습니다. a[1] = 0;

이해가 잘 안됩니다. 평가는 어떻게 이루어 졌습니까?


19
왜 이런 일이 일어나는지에 대한 아래의 혼란은 많은 사람들이 그것이 명백한 대신에 실제로 무엇을하는지 생각해야 할 것이기 때문에 절대로 이것을하지 말아야 함을 의미합니다.
Martijn 2011

이와 같은 질문에 대한 정답은 "그렇게하지 마십시오!"입니다. 할당은 진술로 취급되어야합니다. 값을 반환하는 표현식으로 할당을 사용하면 컴파일러 오류 또는 최소한 경고가 발생해야합니다. 그러지 마.
Mason Wheeler

답변:


173

사람들이 항상 이것을 오해하기 때문에 이것을 매우 명확하게 말하겠습니다.

하위 표현식의 평가 순서는 연관성 및 우선 순위와 무관 합니다. 연관성과 우선 순위는 연산자 가 실행되는 순서를 결정 하지만 하위 표현식 이 평가 되는 순서는 결정 하지 않습니다 . 귀하의 질문은 하위 표현식 이 평가 되는 순서에 관한 것 입니다.

고려하십시오 A() + B() + C() * D(). 곱셈은 ​​덧셈보다 우선 순위가 높고 덧셈은 왼쪽 연관성이므로 이것은 (A() + B()) + (C() * D()) 첫 번째 덧셈이 두 번째 덧셈 이전에 발생하고 곱셈이 두 번째 덧셈 이전에 발생한다는 것을 알려주 는 것과 동일합니다 . A (), B (), C () 및 D ()가 어떤 순서로 호출되는지 알려주지 않습니다! (또한 곱셈이 첫 번째 덧셈 전후에 발생하는지 여부를 알려주지 않습니다.) 다음과 같이 컴파일하여 우선 순위 및 연관성 규칙을 완벽하게 준수 할 수 있습니다 .

d = D()          // these four computations can happen in any order
b = B()
c = C()
a = A()
sum = a + b      // these two computations can happen in any order
product = c * d
result = sum + product // this has to happen last

모든 우선 순위 및 연관성 규칙이 거기에 따릅니다. 첫 번째 더하기는 두 번째 더하기 전에 발생하고 곱하기는 두 번째 더하기 전에 발생합니다. 분명히 우리는 A (), B (), C () 및 D ()에 대한 호출을 순서 에 관계없이 수행 할 수 있으며 여전히 우선 순위 및 연관성 규칙을 준수합니다!

하위 표현식이 평가되는 순서를 설명하려면 우선 순위 및 연관성 규칙 과 관련이없는 규칙 필요합니다 . Java (및 C #)의 관련 규칙은 "하위 표현식은 왼쪽에서 오른쪽으로 평가됩니다"입니다. A ()가 C ()의 왼쪽에 나타나기 때문에 C ()가 곱셈에 관련되고 A ()가 덧셈에만 관련된다는 사실에 관계없이 A ()가 먼저 평가 됩니다.

이제 질문에 답할 수있는 충분한 정보가 있습니다. 에서 a[b] = b = 0연관성 말의 규칙이 있음을 a[b] = (b = 0);하지만 그 뜻은 아닙니다 b=0첫번째 실행을! 우선 순위 규칙은 인덱싱이 할당보다 우선 순위가 높다고 말하지만 인덱서가 가장 오른쪽 할당보다 먼저 실행된다는 것을 의미하지는 않습니다. .

(업데이트 :이 답변의 이전 버전에는 내가 수정 한 섹션에 작고 실질적으로 중요하지 않은 누락이 있습니다.이 규칙이 Java 및 C #에서 왜 합리적인지 설명하는 블로그 기사를 작성했습니다 .https : // ericlippert.com/2019/01/18/indexer-error-cases/ )

것을 우선 순위와는 단지 우리에게 영의 할당b일어나야 하기 전에 에 할당 a[b], 0이기 때문에 계산하는 색인 작업에 할당 된 값의 할당. 우선 순위와 여부에 대한 연관성 혼자 말 아무것도 a[b]평가 또는 후에b=0 .

다시 말하지만 이것은 다음과 같습니다 A()[B()] = C().-우리가 아는 것은 할당 전에 인덱싱이 이루어져야한다는 것입니다. A (), B () 또는 C ()가 우선 순위와 연관성을 기반으로 먼저 실행되는지 여부는 알 수 없습니다 . 우리는 그것을 말할 또 다른 규칙이 필요합니다.

규칙은 "먼저 무엇을할지 선택할 수있을 때 항상 왼쪽에서 오른쪽으로 이동"하는 것입니다. 그러나이 특정 시나리오에는 흥미로운 주름이 있습니다. null 컬렉션 또는 범위를 벗어난 인덱스로 인해 발생한 예외의 부작용이 할당의 왼쪽 계산의 일부로 간주됩니까, 아니면 할당 자체 계산의 일부로 간주됩니까? 자바는 후자를 선택합니다. (물론 이것은 올바른 코드가 null을 역 참조하거나 처음부터 잘못된 인덱스를 전달하지 않기 때문에 코드가 이미 잘못된 경우 에만 중요한 차이입니다 .)

그래서 어떻게 되나요?

  • a[b]의 왼쪽에 b=0소위, a[b]실행은 첫째 , 결과 a[1]. 그러나 유효성 확인 인덱싱 작업 이 지연됩니다.
  • 그런 다음 b=0발생합니다.
  • 그런 다음 a유효하고 a[1]범위 내에 있는 확인이 발생합니다.
  • 값 할당은 a[1]마지막 에 발생합니다.

따라서이 특정 경우에는 처음에 올바른 코드에서 발생하지 않아야하는 드문 오류 사례에 대해 고려해야 할 몇 가지 미묘한 점이 있지만 일반적으로 추론 할 수 있습니다. 왼쪽에있는 일이 오른쪽에있는 일보다 먼저 발생합니다. . 그것이 당신이 찾고있는 규칙입니다. 우선 순위와 연관성에 대한 이야기는 혼란스럽고 관련성이 없습니다.

사람들은이 문제를 항상 잘못 이해 합니다 . 더 잘 알아야하는 사람들도 마찬가지입니다. 규칙을 잘못 언급 한 프로그래밍 책을 너무 많이 편집 했으므로 많은 사람들이 우선 순위 / 연관성 및 평가 순서 사이의 관계에 대해 완전히 잘못된 믿음을 가지고 있다는 사실, 즉 실제로 그러한 관계가 없다는 것은 놀라운 일이 아닙니다. ; 그들은 독립적입니다.

이 주제가 관심이있는 경우 추가 읽기를 위해 주제에 대한 내 기사를 참조하십시오.

http://blogs.msdn.com/b/ericlippert/archive/tags/precedence/

C #에 관한 것이지만 대부분의 내용은 Java에도 동일하게 적용됩니다.


6
개인적으로 저는 첫 번째 단계에서 우선 순위와 연관성을 사용하여 표현 트리를 만드는 멘탈 모델을 선호합니다. 그리고 두 번째 단계에서는 루트로 시작하는 트리를 재귀 적으로 평가합니다. 노드 평가 : 왼쪽에서 오른쪽으로 직계 자식 노드를 평가 한 다음 메모 자체를 평가합니다. | 이 모델의 한 가지 장점은 이항 연산자가 부작용이있는 경우를 사소하게 처리한다는 것입니다. 그러나 가장 큰 장점은 단순히 내 두뇌에 더 잘 맞는다는 것입니다.
CodesInChaos 2011-07-23

C ++이 이것을 보장하지 않는다는 것이 맞습니까? 파이썬은 어떻습니까?
Neil G

2
@Neil : C ++는 평가 순서에 대해 어떤 것도 보장하지 않으며 결코 그렇게하지 않았습니다. (C도 아닙니다.) 파이썬은 우선 순위에 따라 엄격하게 보장합니다. 다른 모든 것과 달리 할당은 R2L입니다.
Donal Fellows

2
@aroth 당신은 단지 혼란스럽게 들립니다. 그리고 우선 순위 규칙은 자녀가 부모보다 먼저 평가되어야 함을 의미합니다. 그러나 그들은 아이들이 평가되는 순서에 대해서는 아무 말도하지 않습니다. Java와 C #은 왼쪽에서 오른쪽으로, C와 C ++는 정의되지 않은 동작을 선택했습니다.
CodesInChaos 2011-07-24

6
@noober : 좋아요, 고려 : M (A () + B (), C () * D (), E () + F ()). 하위식이 어떤 순서로 평가되기를 바라십니까? 곱셈이 더하기보다 우선 순위가 높기 때문에 C ()와 D ()를 A (), B (), E () 및 F () 전에 평가해야합니까? "분명히"순서가 달라야한다고 말하기 쉽습니다. 모든 경우를 포괄하는 실제 규칙을 만드는 것은 다소 어렵습니다. C # 및 Java 설계자는 간단하고 설명하기 쉬운 규칙 인 "왼쪽에서 오른쪽으로 이동"을 선택했습니다. 이에 대한 대체 제안은 무엇이며, 규칙이 더 낫다고 생각하는 이유는 무엇입니까?
Eric Lippert

32

그럼에도 불구하고 Eric Lippert의 뛰어난 답변은 다른 언어에 대해 이야기하고 있기 때문에 적절하게 도움이되지 않습니다. 이것은 Java입니다. 여기서 Java 언어 사양은 의미론에 대한 명확한 설명입니다. 특히, §15.26.1=연산자에 대한 평가 순서를 설명하기 때문에 관련이 있습니다 (우리 모두는 그것이 오른쪽 연관성이라는 것을 알고 있습니까?). 이 질문에서 우리가 관심을 갖는 부분으로 조금 줄이십시오.

왼쪽 피연산자식이 배열 액세스 식 ( §15.13 )이면 여러 단계가 필요합니다.

  • 먼저, 왼쪽 피연산자 배열 액세스 표현식의 배열 참조 하위 표현식이 평가됩니다. 이 평가가 갑자기 완료되면 같은 이유로 할당 표현식이 갑자기 완료됩니다. 인덱스 하위 표현식 (왼쪽 피연산자 배열 액세스 표현식의) 및 오른쪽 피연산자는 평가되지 않으며 할당이 발생하지 않습니다.
  • 그렇지 않으면 왼쪽 피연산자 배열 액세스 식의 인덱스 하위식이 평가됩니다. 이 평가가 갑자기 완료되면 같은 이유로 할당식이 갑자기 완료되고 오른쪽 피연산자가 평가되지 않고 할당이 발생하지 않습니다.
  • 그렇지 않으면 오른쪽 피연산자가 평가됩니다. 이 평가가 갑자기 완료되면 할당식이 같은 이유로 갑자기 완료되고 할당이 발생하지 않습니다.

[… 그런 다음 과제 자체의 실제 의미를 설명합니다. 여기서는 간결하게 무시할 수 있습니다.…]

요컨대, Java는 연산자 또는 메소드 호출에 대한 인수 내에서 거의 정확히 왼쪽에서 오른쪽 으로 매우 밀접하게 정의 된 평가 순서를 가지고 있습니다. 배열 할당은 더 복잡한 경우 중 하나이지만 여전히 L2R입니다. (JLS는 이러한 종류의 복잡한 의미 제약 조건이 필요한 코드를 작성 하지 말 것을 권장하며 , 저도 그렇게합니다 : 문당 하나의 할당만으로도 충분한 문제를 겪을 수 있습니다!)

C 및 C ++는이 영역에서 Java와 확실히 다릅니다. 언어 정의는 더 많은 최적화를 가능하게하기 위해 의도적으로 평가 순서를 정의하지 않은 상태로 둡니다. C #은 분명히 Java와 비슷하지만 공식적인 정의를 가리킬 수있을만큼 관련 문헌을 잘 모릅니다. (이것은 언어에 따라 실제로 다릅니다. Ruby는 Tcl과 마찬가지로 엄격하게 L2R입니다. 여기에 관련되지 않은 이유로 할당 연산자 자체부족하지만 Python은 L2R이지만 할당관련하여 R2L입니다. .)


11
그래서 당신이 말하는 것은 에릭의 대답이 잘못되었다는 것입니다. Java가 구체적으로 그가 말한 그대로 정의하기 때문입니까?
구성자 '

8
Java (및 C #)의 관련 규칙은 "하위 표현식은 왼쪽에서 오른쪽으로 평가됩니다" 입니다. 두 가지 모두에 대해 이야기하는 것처럼 들립니다.
구성자

2
여기에서 약간 혼란 스럽습니다. 이것은 Eric Lippert의 위 답변을 덜 사실로 만들까요? 아니면 왜 그것이 사실인지에 대한 특정 참조를 인용하고 있습니까?
GreenieMeanie

6
@Greenie : 에릭의 대답은 사실이지만 제가 언급했듯이이 영역의 한 언어에서 통찰력을 가져다가주의하지 않고 다른 언어에 적용 할 수는 없습니다. 그래서 결정적인 출처를 인용했습니다.
Donal Fellows

1
이상한 점은 왼쪽 변수가 해결되기 전에 오른쪽이 평가된다는 것입니다. 에 a[-1]=c, c평가, 이전에 -1무효로 인식되고 있습니다.
ZhongYu 2015

5
a[b] = b = 0;

1) 배열 인덱싱 연산자는 할당 연산자보다 우선 순위가 높습니다 ( 이 답변 참조 ).

(a[b]) = b = 0;

2) 15.26에 따름. JLS의 할당 연산자

12 개의 할당 연산자가 있습니다. 모두 구문 상 오른쪽 연관입니다 (오른쪽에서 왼쪽으로 그룹화 됨). 따라서 a = b = c는 a = (b = c)를 의미하며, c 값을 b에 할당 한 다음 b 값을 a에 할당합니다.

(a[b]) = (b=0);

3) 15.7에 따르면. JLS 평가 순서

Java 프로그래밍 언어는 연산자의 피연산자가 특정 평가 순서, 즉 왼쪽에서 오른쪽으로 평가되는 것처럼 보이도록 보장합니다.

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

그래서:

a) (a[b])먼저 평가a[1]

b) 다음 (b=0) 평가0

c) (a[1] = 0)마지막으로 평가


1

코드는 다음과 같습니다.

int[] a = {4,4};
int b = 1;
c = b;
b = 0;
a[c] = b;

결과를 설명합니다.


7
문제는 그 이유입니다.
Mat

@Mat 대답은 이것이 질문에 제공된 코드를 고려할 때 후드 아래에서 일어나는 일이기 때문입니다. 그것이 평가가 일어나는 방법입니다.
Jérôme Verstrynge 2011

1
네, 알아요. IMO를 통해 여전히 질문에 대답하지 않기 때문에 이것이 평가가 발생하는 이유 입니다.
Mat

1
@Mat '왜 이렇게 평가가 발생합니까?' 질문이 아닙니다. '평가는 어떻게 되었습니까?' 질문입니다.
Jérôme Verstrynge 2011

1
@JVerstry : 어떻게 동등하지 않습니까? 왼쪽 피연산자의 배열 기준 서브 표현식 이다 좌단 피연산자. 따라서 "가장 왼쪽부터 먼저 수행"이라고 말하는 것은 "배열 참조를 먼저 수행"이라고 말하는 것과 정확히 동일합니다. Java 스펙 작성자가이 특정 규칙을 설명 할 때 불필요하게 장황하고 중복되는 것을 선택했다면 그들에게 좋습니다. 이런 종류의 것은 혼란스럽고 덜 장황하기보다는 더 많아야합니다. 그러나 내 간결한 특성화가 prolix 특성화와 의미 적으로 어떻게 다른지 알 수 없습니다.
Eric Lippert

0

아래에서 더 자세한 예를 살펴보십시오.

일반적인 경험 법칙 :

이러한 질문을 해결할 때 읽을 수있는 우선 순위 규칙 및 연관성 표를 갖는 것이 가장 좋습니다. http://introcs.cs.princeton.edu/java/11precedence/와

다음은 좋은 예입니다.

System.out.println(3+100/10*2-13);

질문 : 위 라인의 출력은 무엇입니까?

답변 : 우선 순위 및 연관성 규칙 적용

1 단계 : 우선 순위 규칙에 따라 : / 및 * 연산자가 +-연산자보다 우선합니다. 따라서이 방정식을 실행하는 시작점은 다음과 같이 좁혀집니다.

100/10*2

2 단계 : 규칙 및 우선 순위에 따라 : / 및 *는 우선 순위가 동일합니다.

/ 및 * 연산자는 우선 순위가 동일하므로 해당 연산자 간의 연관성을 살펴볼 필요가 있습니다.

이 두 연산자의 연관성 규칙에 따라 왼쪽에서 오른쪽으로 방정식을 실행하기 시작합니다. 즉, 100/10이 먼저 실행됩니다.

100/10*2
=100/10
=10*2
=20

3 단계 : 방정식은 이제 다음 실행 상태에 있습니다.

=3+20-13

규칙과 우선 순위에 따르면 +와-는 우선 순위가 같습니다.

이제 연산자 + 및-연산자 간의 연관성을 살펴볼 필요가 있습니다. 이 두 특정 연산자의 연관성에 따라 LEFT에서 RIGHT로 방정식 실행을 시작합니다. 즉, 3 + 20이 먼저 실행됩니다.

=3+20
=23
=23-13
=10

10은 컴파일시 올바른 출력입니다.

다시 말하지만, http://introcs.cs.princeton.edu/java/11precedence/ 와 같은 질문을 해결할 때 우선 순위 규칙 및 연관성에 대한 표를 가지고있는 것이 중요합니다.


1
"+ 및-연산자 간의 연관성"이 "RIGHT TO LEFT"라고 말하고 있습니다. 그 논리를 사용하고 평가하십시오 10 - 4 - 3.
Pshemo

1
이 실수는 introcs.cs.princeton.edu/java/11precedence의 맨 위에 +단항 연산자 (오른쪽에서 왼쪽으로의 연관성이 있음)이지만 덧셈 + 및-는 곱셈 * / % 왼쪽과 같다는 사실로 인해 발생할 수 있다고 생각합니다. 오른쪽 연관성에.
Pshemo

잘 발견되고 그에 따라 수정되었습니다. 감사합니다 Pshemo
Mark Burleigh

이 답변은 우선 순위와 연관성을 설명하지만 Eric Lippert가 설명했듯이 질문은 매우 다른 평가 순서에 관한 것입니다. 사실 이것은 질문에 대한 답이 아닙니다.
파비오는 분석 재개 모니카 말한다
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.