[java] 이 Java 코드가 컴파일되는 이유는 무엇입니까?

메서드 또는 클래스 범위에서 아래 줄은 경고와 함께 컴파일됩니다.

int x = x = 1;

변수가 기본값을 가져 오는 클래스 범위 에서 다음은 ‘정의되지 않은 참조’오류를 제공합니다.

int x = x + 1;

첫 번째 x = x = 1는 동일한 ‘정의되지 않은 참조’오류로 끝나야 하지 않습니까? 아니면 두 번째 줄 int x = x + 1을 컴파일해야할까요? 아니면 내가 놓친 것이 있습니까?



답변

tl; dr

들어 필드 , int b = b + 1때문에 불법 b에 불법 전방 참조입니다 b. int b = this.b + 1불만없이 컴파일되는를 작성하여 실제로이 문제를 해결할 수 있습니다 .

의 경우 지역 변수 , int d = d + 1때문에 불법 d사용하기 전에 초기화되지 않았습니다. 입니다 하지 항상 기본 초기화되어 필드의 경우.

컴파일을 시도하여 차이를 볼 수 있습니다.

int x = (x = 1) + x;

필드 선언 및 지역 변수 선언으로. 전자는 실패하지만 후자는 의미가 다르기 때문에 성공할 것입니다.

소개

우선, 필드 및 지역 변수 이니셜 라이저에 대한 규칙이 매우 다릅니다. 따라서이 답변은 두 부분으로 규칙을 다룰 것입니다.

이 테스트 프로그램을 다음과 같이 사용합니다.

public class test {
    int a = a = 1;
    int b = b + 1;
    public static void Main(String[] args) {
        int c = c = 1;
        int d = d + 1;
    }
}

의 선언 b이 유효하지 않으며 오류와 함께 실패합니다 illegal forward reference.
의 선언 d이 유효하지 않으며 오류와 함께 실패합니다 variable d might not have been initialized.

이러한 오류가 다르다는 사실은 오류의 원인도 다르다는 것을 암시해야합니다.

필드

Java의 필드 이니셜 라이저는 JLS §8.3.2 , 필드 초기화에 의해 관리됩니다 .

필드 의 범위JLS §6.3 , 선언 범위에 정의되어 있습니다.

관련 규칙은 다음과 같습니다.

  • m클래스 유형 C (§8.1.6)에서 선언되거나 상속 된 멤버 선언 의 범위는 중첩 된 유형 선언을 포함하여 C의 전체 본문입니다.
  • 인스턴스 변수에 대한 초기화 표현식은 클래스에서 선언되거나 상속 된 모든 정적 변수의 간단한 이름을 사용할 수 있습니다. 선언이 나중에 텍스트로 발생하는 변수도 사용할 수 있습니다.
  • 사용 후 선언이 텍스트로 나타나는 인스턴스 변수의 사용은 이러한 인스턴스 변수가 범위 내에 있더라도 때때로 제한됩니다. 인스턴스 변수에 대한 순방향 참조를 관리하는 정확한 규칙은 §8.3.2.3을 참조하십시오.

§8.3.2.3 내용 :

멤버 선언은 멤버가 클래스 또는 인터페이스 C의 인스턴스 (각각 정적) 필드이고 다음 조건이 모두 충족되는 경우에만 사용되기 전에 텍스트로 나타나야합니다.

  • 사용은 C의 인스턴스 (각각 정적) 변수 이니셜 라이저 또는 C의 인스턴스 (각각 정적) 이니셜 라이저에서 발생합니다.
  • 사용법은 과제의 왼쪽에 있지 않습니다.
  • 사용법은 간단한 이름을 사용합니다.
  • C는 사용법을 포함하는 가장 안쪽의 클래스 또는 인터페이스입니다.

특정 경우를 제외하고 선언되기 전에 실제로 필드를 참조 할 수 있습니다. 이러한 제한은 다음과 같은 코드를 방지하기위한 것입니다.

int j = i;
int i = j;

컴파일에서. 자바 스펙에 따르면 “위의 제한 사항은 컴파일 타임에 순환 또는 잘못된 형식의 초기화를 포착하도록 설계되었습니다.”

이 규칙은 실제로 무엇으로 요약됩니까?

요컨대, 규칙은 기본적으로 (a) 참조가 이니셜 라이저에 있고 (b) 참조가 할당되지 않은 경우, (c) 참조가 해당 필드에 대한 참조보다 먼저 필드를 선언 해야 한다고 말합니다 . 간단한 이름 (같은 한정자 없음 this.) 및 (d) 내부 클래스 내에서 액세스되지 않습니다. 따라서 네 가지 조건을 모두 충족하는 순방향 참조는 불법이지만 하나 이상의 조건에서 실패한 순방향 참조는 괜찮습니다.

int a = a = 1;(b)를 위반하여 컴파일 됨 : 참조 a 할당되고 있으므로 의 완전한 선언 a에 앞서 참조하는 것이 합법적 a입니다.

int b = this.b + 1또한 (c)를 위반하기 때문에 컴파일됩니다 : 참조 this.b는 단순한 이름이 아닙니다 (로 한정됨 this.). 이 이상한 구조는 this.b값이 0 이기 때문에 여전히 완벽하게 잘 정의되어 있습니다.

따라서 기본적으로 이니셜 라이저 내의 필드 참조에 대한 제한으로 int a = a + 1인해 성공적으로 컴파일되지 않습니다.

final 은 여전히 ​​잘못된 포워드 참조 이므로 필드 선언 int b = (b = 1) + b이 컴파일 되지 않습니다b .

지역 변수

지역 변수 선언은 JLS §14.4 , 지역 변수 선언문 에 의해 관리됩니다 .

지역 변수 의 범위JLS §6.3 , 선언 범위에 정의되어 있습니다 .

  • 블록 (§14.4)에서 지역 변수 선언의 범위는 선언이 나타나는 나머지 블록으로, 자체 이니셜 라이저로 시작하고 지역 변수 선언문의 오른쪽에있는 추가 선언자를 포함합니다.

이니셜 라이저는 선언되는 변수의 범위 내에 있습니다. 그렇다면 왜 int d = d + 1;컴파일 되지 않습니까?

그 이유는 명확한 할당 에 대한 Java의 규칙 ( JLS §16 ) 때문입니다. 명확한 할당은 기본적으로 지역 변수에 대한 모든 액세스에는 해당 변수에 대한 선행 할당이 있어야하며 Java 컴파일러는 루프와 분기를 검사하여 항상 사용 전에 할당이 발생 하는지 확인합니다 (이것이 명확한 할당에 전용 사양 섹션이있는 이유입니다. 그것에). 기본 규칙은 다음과 같습니다.

  • 지역 변수 또는 빈 마지막 필드의 모든 액세스의 경우 x, x확실히 액세스하기 전에 할당, 또는 컴파일 타임 오류가 발생해야합니다.

에서 int d = d + 1;에 대한 액세스 d는 로컬 변수로 해결되지만 d이전에 할당되지 않았으므로 d컴파일러에서 오류를 발생시킵니다. 이어 int c = c = 1, c = 1먼저 일어나는 대입 c한 후, 및 c(1)이 할당의 결과로 초기화된다.

명확한 할당 규칙으로 인해 지역 변수 선언 int d = (d = 1) + d; 성공적으로 컴파일 됩니다 ( 필드 선언 과 달리int b = (b = 1) + b ) . d최종 d에 도달 할 때 확실히 할당 되기 때문 입니다 .


답변

int x = x = 1;

다음과 같다

int x = 1;
x = x; //warning here

에있는 동안

int x = x + 1; 

먼저 계산이 필요 x+1하지만 x의 값을 알 수 없으므로 오류가 발생합니다 (컴파일러는 x의 값을 알 수 없음을 알고 있음)


답변

대략 다음과 같습니다.

int x;
x = 1;
x = 1;

첫째, int <var> = <expression>;항상

int <var>;
<var> = <expression>;

이 경우 표현식은 x = 1이며 이는 또한 명령문입니다. x = 1var x가 이미 선언되었으므로 유효한 문 입니다. 또한 값이 1 인 표현식이며 x다시 할당 됩니다.


답변

자바 또는 모든 현대 언어에서 할당은 오른쪽에서 나옵니다.

두 개의 변수 x와 y가 있다고 가정합니다.

int z = x = y = 5;

이 명령문은 유효하며 컴파일러가이를 분할하는 방법입니다.

y = 5;
x = y;
z = x; // which will be 5

하지만 당신의 경우

int x = x + 1;

컴파일러는 이와 같이 분할되므로 예외가 발생했습니다.

x = 1; // oops, it isn't declared because assignment comes from the right.


답변

int x = x = 1; 같지 않음 :

int x;
x = 1;
x = x;

javap가 다시 도움이됩니다. 다음은이 코드에 대해 생성 된 JVM 명령어입니다.

0: iconst_1    //load constant to stack
1: dup         //duplicate it
2: istore_1    //set x to constant
3: istore_1    //set x to constant

더 좋아 :

int x = 1;
x = 1;

여기에 정의되지 않은 참조 오류가 발생할 이유가 없습니다. 이제 초기화 전에 변수 사용이 있으므로이 코드는 사양을 완전히 준수합니다. 사실 변수의 사용은 전혀없고 할당 만 있습니다. 그리고 JIT 컴파일러는 더 나아가 그러한 구조를 제거 할 것입니다. 솔직히 말해서이 코드가 JLS의 변수 초기화 및 사용 사양에 어떻게 연결되어 있는지 이해하지 못합니다. 사용에 문제가 없습니다. 😉

내가 틀렸다면 수정하십시오. 많은 JLS 단락을 참조하는 다른 답변이 왜 그렇게 많은 장점을 수집하는지 알 수 없습니다. 이 단락은이 경우와 공통점이 없습니다. 두 개의 직렬 할당 만 있으면됩니다.

우리가 쓰면 :

int b, c, d, e, f;
int a = b = c = d = e = f = 5;

동일하다:

f = 5
e = 5
d = 5
c = 5
b = 5
a = 5

대부분의 표현식은 재귀없이 변수에 하나씩 할당됩니다. 원하는 방식으로 변수를 엉망으로 만들 수 있습니다.

a = b = c = f = e = d = a = a = a = a = a = e = f = 5;


답변

에서 int x = x + 1;당신은 X에 1을 추가, 그래서의 가치 무엇 x그것은 아직 생성 아니에요.

그러나 in int x=x=1;은 1을에 할당하기 때문에 오류없이 컴파일됩니다 x.


답변

첫 번째 코드에는 =플러스 대신 두 번째 코드가 포함되어 있습니다 . 두 번째 코드는 어느 곳에서도 컴파일되지 않습니다.