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


96

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

int x = x = 1;

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

int x = x + 1;

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


1
static에서와 같이 클래스 범위 변수에 키워드를 추가 static int x = x + 1;하면 동일한 오류가 발생합니까? C #에서는 정적이든 비 정적이든 차이가 있기 때문입니다.
Jeppe Stig Nielsen

static int x = x + 1Java에서 실패합니다.
Marcin

1
C #에서 int a = this.a + 1;int b = 1; int a = b + 1;클래스 범위 (Java에서 모두 괜찮음) 모두 실패합니다. 아마도 §17.4.5.2- "인스턴스 필드에 대한 변수 이니셜 라이저가 생성중인 인스턴스를 참조 할 수 없습니다." 어딘가에서 명시 적으로 허용되는지는 모르겠지만 정적에는 그러한 제한이 없습니다. 자바의 규칙은 다른과 static int x = x + 1같은 이유로 실패 int x = x + 1하지
MSAM을

바이트 코드가있는 해당 anwser는 의심을 제거합니다.
rgripper

답변:


101

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에 도달 할 때 확실히 할당 되기 때문 입니다 .


참조에 대해 +1을 받았지만 "int a = a = 1; (b)를 위반하기 때문에 컴파일합니다."라는 문구가 잘못되었다고 생각합니다. 4 가지 요구 사항 중 하나를 위반하면 컴파일되지 않습니다. 이 때문에 그렇지 않습니다 IS (많은 여기에 도움이되지 않습니다 JLS의 표현에 이중 부정) 할당의 왼쪽에. In int b = b + 1b는 과제의 오른쪽 (왼쪽이 아님)에 있으므로이를 위반합니다 ...
msam

... 나는 너무 확실의 아니에요 무엇 다음과 같습니다 선언이이 경우에 내가 선언이 할당되기 전에 「텍스트로 "나타나지 않는 생각, 할당하기 전에 텍스트로 표시되지 않을 경우 그 네 조건이 충족되어야 int x = x = 1하는, 이 중 어느 것도 적용되지 않는 경우.
msam 2013

@msam : 약간 혼란 스럽지만 기본적으로 포워드 레퍼런스를 만들기 위해서는 네 가지 조건 중 하나를 위반해야합니다. 전방 참조 네 가지 조건을 모두 충족 하면 불법입니다.
nneonneo 2013

@msam : 또한 전체 선언은 이니셜 라이저 후에 만 ​​적용됩니다.
nneonneo 2013

@mrfishie : 큰 대답이지만 Java 사양에는 놀라 울 정도의 깊이가 있습니다. 질문은 겉으로보기에 그렇게 간단하지 않습니다. (저는 한 번에 Java 컴파일러의 하위 집합을 작성했기 때문에 JLS의 많은 내용에 대해 잘 알고 있습니다.)
nneonneo 2013

86
int x = x = 1;

다음과 같다

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

에있는 동안

int x = x + 1; 

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


4
여기에 OpenSauce의 오른쪽 연관성에 대한 힌트가 더해져 매우 유용하다고 생각했습니다.
TobiMcNamobi

1
할당의 반환 값은 변수 값이 아니라 할당되는 값이라고 생각했습니다.
zzzzBov

2
@zzzzBov가 정확합니다. int x = x = 1;에 해당 int x = (x = 1), 없습니다 x = 1; x = x; . 이렇게하면 컴파일러 경고가 표시되지 않아야합니다.
nneonneo

int x = x = 1;int로 등가이야 x = (x = 1)인해 오른쪽 연관성 =운영자
Grijesh 차우

1
@nneonneo과 int x = (x = 1)동등하다 int x; x = 1; x = x;(변수 선언, 필드의 초기화, 상기 평가의 결과를 변수 할당 평가) 때문에 경고
MSAM

41

대략 다음과 같습니다.

int x;
x = 1;
x = 1;

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

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

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


좋아, 그러나 당신이 말한 것처럼 갔다면 왜 클래스 범위에서 두 번째 문이 오류를 제공합니까? 나는 당신이 0int에 대한 기본값을 얻는다는 것을 의미 하므로 결과가 undefined reference.
Marcin

@izogfif 답변을 살펴보십시오. C ++ 컴파일러가 변수에 기본값을 할당하기 때문에 작동하는 것처럼 보입니다. Java가 클래스 수준 변수에 대해 수행하는 것과 동일한 방식입니다.
Marcin

@Marcin : Java에서 int는 지역 변수 일 때 0으로 초기화 되지 않습니다 . 멤버 변수 인 경우에만 0으로 초기화됩니다. 따라서 두 번째 줄 x + 1에는 x초기화되지 않았기 때문에 정의 된 값이 없습니다 .
OpenSauce 2013-04-04

1
@OpenSauce 그러나이 x 된다 ( "클래스 범위") 멤버 변수로서 정의.
Jacob Raihle 2013-04-04

@JacobRaihle : 아 좋아요, 그 부분을 발견하지 못했습니다. 명시적인 초기화 명령이있는 경우 컴파일러에서 var를 0으로 초기화하는 바이트 코드가 생성되는지 확실하지 않습니다. 여기에 클래스 및 객체 초기화에 대해 자세히 설명하는 기사가 있지만 정확한 문제는 다루지 않는다고 생각합니다. javaworld.com/jw-11-2001/jw-1102-java101.html
OpenSauce

12

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

두 개의 변수 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.

경고는 X = X되지 X = 1에
아심 Ghaffar

8

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;

7

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

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


5

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


5

두 번째 코드에서 x는 선언 전에 사용되는 반면 첫 번째 코드에서는 의미가 없지만 유효한 두 번 할당됩니다.


5

단계별로 분해 해 보겠습니다.

int x = x = 1

x = 1, 변수 x에 1을 할당합니다.

int x = x, x가 무엇인지, int로 할당하십시오. x는 이전에 1로 할당되었으므로 중복 방식이기는하지만 1을 유지합니다.

잘 컴파일됩니다.

int x = x + 1

x + 1, 변수 x에 하나를 추가합니다. 그러나 x가 정의되지 않으면 컴파일 오류가 발생합니다.

int x = x + 1, 따라서이 줄은 등호의 오른쪽 부분이 할당되지 않은 변수에 하나를 추가하여 컴파일되지 않으므로 오류를 컴파일합니다.


아니요, =연산자 가 두 개인 경우 오른쪽 연관 이므로 int x = (x = 1);.
Jeppe Stig Nielsen

아, 주문 취소 미안합니다. 거꾸로 했어야 했어. 나는 지금 그것을 바꿨다.
steventnorris

3

두 번째 int x=x=1는 x에 값을 할당하기 때문에 컴파일되지만 다른 경우 int x=x+1에는 변수 x가 초기화되지 않습니다. Java에서 로컬 변수는 기본값으로 초기화되지 않습니다. 참고 int x=x+1클래스 범위에서도 ( )이면 변수가 생성되지 않으므로 컴파일 오류가 발생합니다.


2
int x = x + 1;

Visual Studio 2008에서 경고와 함께 성공적으로 컴파일됩니다.

warning C4700: uninitialized local variable 'x' used`

2
인터 레스트. C / C ++입니까?
Marcin

@Marcin : 네, C ++입니다. @msam : 죄송합니다. c대신 태그 를 본 것 java같지만 분명히 다른 질문이었습니다.
izogfif

C ++에서 컴파일러는 기본 유형에 대한 기본값을 할당하기 때문에 컴파일됩니다. 사용 bool y;하고 y==truefalse를 반환합니다.
Sri Harsha Chilakapati

@SriHarshaChilakapati, C ++ 컴파일러의 표준인가요? void main() { int x = x + 1; printf("%d ", x); }Visual Studio 2008에서 컴파일 할 때 Debug에서 예외가 발생 Run-Time Check Failure #3 - The variable 'x' is being used without being initialized.하고 Release 1896199921에서는 콘솔에 번호가 인쇄되기 때문입니다.
izogfif 2013-04-04

1
@SriHarshaChilakapati 다른 언어에 대해 말하기 : C #에서 static필드 (클래스 수준 정적 변수)의 경우 동일한 규칙이 적용됩니다. 예를 들어 public static int x = x + 1;Visual C #에서 경고없이 컴파일 된 것으로 선언 된 필드 입니다. Java에서도 동일합니까?
Jeppe Stig Nielsen

2

x는 x = x + 1; 에서 초기화되지 않습니다 .

Java 프로그래밍 언어는 정적으로 형식화되어 있으므로 모든 변수를 사용하기 전에 먼저 선언해야합니다.

참조 기본 데이터 유형을


3
값을 사용하기 전에 변수를 초기화해야하는 필요성은 정적 유형과 관련이 없습니다. 정적으로 형식화 됨 : 변수의 형식을 선언해야합니다. 사용 전 초기화 : 값을 사용하려면 먼저 값이 있어야합니다.
Jon Bright

@JonBright : 변수 유형을 선언해야하는 필요성은 정적 유형과 관련이 없습니다. 예를 들어 유형 유추를 사용하는 정적으로 유형이 지정된 언어가 있습니다.
hammar

@hammar, 내가보기에, 당신은 두 가지 방법으로 그것을 주장 할 수 있습니다 : 타입 추론을 사용하면 시스템이 추론 할 수있는 방식으로 변수의 타입을 암시 적으로 선언합니다. 또는 유형 추론은 세 번째 방법으로, 변수는 런타임에 동적으로 유형이 지정되지 않지만 사용 및 추론에 따라 소스 수준에 있습니다. 어느 쪽이든 진술은 사실로 유지됩니다. 하지만 당신 말이 맞습니다. 저는 다른 유형 시스템에 대해 생각하지 않았습니다.
Jon Bright

2

코드가 실제로 작동하는 방식 때문에 코드 줄이 경고와 함께 컴파일되지 않습니다. 코드를 실행할 때 int x = x = 1Java는 먼저 x정의 된대로 변수를 만듭니다 . 그런 다음 할당 코드 ( x = 1)를 실행합니다 . 때문에 x이미 정의되어, 시스템 설정 오류가없는 x즉 지금의 값이기 때문에,이 반환 1로 값 1 x. 따라서 x이제 마침내 1로 설정됩니다.
Java는 기본적 으로 다음과 같이 코드를 실행합니다.

int x;
x = (x = 1); // (x = 1) returns 1 so there is no error

그러나 두 번째 코드 부분 int x = x + 1에서는 + 1x을 정의 해야하지만 그때까지는 그렇지 않습니다. 할당 문은 항상 오른쪽에 =있는 코드가 먼저 실행됨을 의미하므로 x정의되지 않았 으므로 코드가 실패 합니다. Java는 다음과 같은 코드를 실행합니다.

int x;
x = x + 1; // this line causes the error because `x` is undefined

-1

Complier는 오른쪽에서 왼쪽으로 문장을 읽고 그 반대를 수행하도록 설계했습니다. 그래서 처음에는 짜증이났습니다. 이것을 오른쪽에서 왼쪽으로 문장 (코드)을 읽는 습관으로 만들면 그런 문제가 없을 것입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.