자바 인스턴스 변수와 지역 변수 초기화와 관련하여..

2013-06-28 12:44

변수 초기화와 관련하여 자바로 프로그래밍을 할 때 특별히 신경쓰지 않는다. 나 또한 컴파일 에러 발생하면 초기화하고 그렇지 않으면 초기화하지 않았다. 질문 들어가 보자.

우리들은 흔히 다음과 같이 변수를 선언하면 기본 값으로 초기화되는 것으로 알고 있다.

boolean a; => false로 초기화 int b; => 0으로 초기화 String c; => null로 초기화

그렇다면 다음 코드를 보자.

public class Test {
    boolean a;
    
    void test() {
        System.out.println(a);
        
        boolean b;
        System.out.println(b);
    }
}

위 소스 코드를 컴파일 해보면 a를 출력하는 부분은 정상적으로 컴파일 된다. 하지만 b를 출력하는 부분에서 컴파일 에러가 발생한다. 우리가 알고 있기로 b 또한 false로 초기화가 되기 때문에 test() 메소드를 실행하면 false, false 값이 나올 것으로 예상했는데 그냥 컴파일 에러가 발행한다. 이유가 뭘까?

0개의 의견 from FB

12개의 의견 from SLiPP

2013-06-28 13:02

http://docs.oracle.com/javase/specs/jls/se5.0/html/defAssign.html

"Each local variable (§14.4) and every blank final (§4.12.4) field (§8.3.1.2) must have a definitely assigned value when any access of its value occurs."

스펙에 그렇게 명시되어 있습니다. 왜 스펙을 그렇게 정했을까... 멤버 변수는 쓰이기 전에 초기화 됐는지 컴파일러가 체크할 수가 없죠. 로컬 변수는 컴파일러가 체크할 수 있으니 명시적으로 초기화하도록 강제한 것 같습니다.

2013-06-28 14:30

오히려 에러를 내는 것이 자바 스타일에 맞다고 봅니다. 비관적인 언어이고 컴파일 단계에서 오류를 최대한 잡아내겠다는 정적 타이핑 언어이니까요. EL님 의견처럼 필드는 컴파일러가 검증할 방법이 없으니 예외적으로 초기화 하는거겠죠.

2013-06-28 19:19

우선 language specification에 정해져 있기 때문에.. 가 답인듯한데요.^^; 그럼 왜 그런 규칙을 정했을까요?

제 생각엔 다음과 같은 이유가 있지 않을까 생각해봅니다.

인스턴스 변수의 경우 객체를 통해 값이 할당/사용되기 때문에 그 범위가 넓습니다. 어디에서든 객체를 통해 또는 객체안에서 값이 변경될터인데 초기값을 변수선언시 또는 생성자에서만 준다는 건 좀 불필요하지 않을까요? 비슷한 맥락에서 final 인스턴스 변수의 경우 한번 설정된 값은 변경이 불가능한데요. 역으로 생각하면 값이 객체를 통해 또는 객체안에서 변경이 불가능하기 때문에 반드시 선언과 동시에 초기값을 할당해주는게 맞겠지요.(선언과 동시에 주거나, 생성자에서 주거나)

로컬 변수의 경우는 선언만 하고 초기값을 할당하지 않아도 사용하지 않는다면 컴파일 에러도 안나는걸로 아는데요. 사용하려면 왜 반드시 초기값 할당을 해야만 할까요? 로컬 변수는 메소드 외부에서 변경될 일도 없거니와 메소드 안에서 사용하기 위함이니 초기값을 할당하는게 당연한게 아닐까합니다. JVM 내에서도 하나의 메소드 실행시 스택 프레임에 push되어 해당 변수가 생성되고 초기화되어 사용되어지고 메소드 종료와 함께 해당 스택 프레임은 pop되버리니깐요. 해당 변수에 대한 참조도 다 없어지고요. 로컬 변수는 어차피 메소드 안에서 쓸 놈이니깐 초기값을 줘. 아니면 컴파일 에러!를 뱉는게 아닐까합니다.

그나저나 스펙으로 정한 당연한 내용인데 정리도 안하고 너무 길게 썻네요^^;;;

2013-06-28 20:06

컴파일러에서 실행파일을 생성할 때 발생할 수 있는 에러의 종류는 크게 2가지인데요. syntax error, semantic error예요.

syntax error는 뜻대로 문법의 틀리다는걸 개발자에게 알려주고요. semantic error는 의미적으로 컴파일러가 친절하게 개발자에게 코드 문맥 확인하라는 에러에요.

semantic error는 컴파일 옵션을 통해서 무시할 수도 있구요. "variable b might not have been initialized"는 컴파일러 semantic error 입니다. 그래서 개발자한테 코드 확인하라고 친절하게 알려주는 거예요.

"컴파일러 개론"에서 배우는 내용입니다.

객체지향 언어에서 instance 변수초기화를 위한 별도의 문법이 불필요하잖아요? 대부분의 객체지향언어에서 Idiom 된 것 같아요.

2013-06-28 22:55

모두들 답변 감사합니다. 그냥 스펙에 그렇게 정의되어 있으니까 그냥 쓰라고 할 수도 있는데 다양한 의견 주셔서 저 또한 많은 것을 배웠습니다. 다양한 의견이 있는데 여기서 의문가는 부분이 있어서 질문을 남겨 봅니다.

로컬 변수의 경우는 다음과 같은 상태이기 때문에 컴파일 에러가 발생할 수 밖에 없는 듯 합니다. JVM의 실행 과정을 함 볼께요.

  • JVM이 로컬 변수로 boolean b = false;와 같이 구현하면 Stack Frame의 로컬 변수 영역에 b를 생성
  • b를 사용하려고 할 때 로컬 변수 영역에 있는 로컬 변수 b를 Operand 영역으로 로드한 후에 실행
  • 그런데 boolean b;와 같이 초기화 없이 구현할 경우 Stack Frame의 로컬 변수 영역에 b를 변수를 생성하지 않더군요.
  • 이런 상태에서 b를 사용하려고 할 경우 로컬 변수 영역에 b가 없기 때문에 프로그램 실행 중 문제가 발생하므로 컴파일 에러로 해결하고 있는 듯 합니다.

@eungju.park.1 @fupfin 제가 컴파일러에 대해 지식이 없다보니 멤버 변수가 초기화 되었는지를 컴파일러가 알 수 없기 때문에 멤버 변수는 초기화하지 않더라도 컴파일 에러를 발생시키지 않는다는 부분이 잘 이해가 되지 않네요. 왜 멤버 변수는 초기화 여부를 알 수 없는지요?

멤버 변수는 컴파일러가 초기화되었는지를 검증할 수 없기 때문에 인스턴스가 생성되는 시점에 초기화가 되어 있지 않은 경우 자동으로 초기화를 한다고 했는데요. 그렇다면 프로그래밍을 할 때 멤버 변수에 대해 초기화를 하는 습관을 가지는 것이 좋다고 볼 수 있을까요? 로컬 변수의 경우에는 초기화 되었는지를 검증할 수 있기 때문에 초기화를 강제하고 있다는 맥락에서 본다면 멤버 변수 또한 그런 걸까요? 저는 멤버 변수 초기화하지 않고 사용 많이 하거든요.

@느림보 네 의견으로 본다면 semantic error이기 때문에 컴파일 옵션을 통해 무시하도록 설정했다고 치자. 그렇게 해서 컴파일을 완료했다. 그 후에 실행을 한다면 위 예에서 boolean b;의 b 값은 false로 출력된다는 이야기인가? 만약 false로 초기화되서 출력이 된다면 멤버 변수와 로컬 변수 모두 런타임시에 특정 값으로 초기화된다고 설명할 수 있겠다. 단, 로컬 변수는 컴파일러가 초기화여부를 체크할 수 있기 때문에 개발자에게 좀 더 강력한 제약을 거는 것으로 보면 될테고..

2013-06-28 23:11

전 그냥 단순히 그런생각이 들었는데..

전역변수는 초기화 되는 시점이 사용시점이 아니기 때문에 들어갈 값이 정해져있지 않은 상황이 있을수 있지만..

지역변수는 선언되는 시점이 바로 사용시점이기도 하니까 그렇게 설계 된거 아닐까요..?

그냥 짧은 생각 ㅎㅎ

2013-06-28 23:47

@자바지기 컴파일러마다 로컬 변수에 대한 초기화가 달리 적용되요. 예로 Objective-C LLVM ARC option인 경우 local variable reference를 nil로 자동으로 초기화해줍니다. GCC인 경우에 local variable reference를 코드상에 nil로 초기화 해줘야 됩니다.

Oracle Java 컴파일러에서 생성된 바이너리가 JVM에서 실행될 때 statck frame의 local variable array를 확인까지는 안 해 봤네요.

instance variable 초기화 여부는 컴파일 단계에서는 알 수 없겠네요. 그래서 Objective-c나 Java나 기본값을 assign하겠네요. Java문법은 기본값으로 초기화하는 방식으로 접근했나보네요.

유익한 내용이네요. instance variable에 대해서 기본값을 assign하고 local variable은 꼭 초기화하는 이유를 생각 못하고 있었네요.

2013-06-29 02:05

자바가 인기를 얻은데엔 여러 이유가 있겠지만 메모리 참조의 이해에 대한 해방도 있는듯합니다.. 기존 큰 인기를 얻은 C언어에 대해 차별점으로 메모리를 자동으로 관리하도록 설계한거죠. 여기에서 해결해야하는건 결코 런타임에 메모리참조오류를 가능한 반드시 막아야했겠죠. 그래서 컴파일터임에 가능한한 초기화하는걸 강제한거같아요. 로컬변수는 스택에 올라가니 컴파일타임에 컴퓨터가 미리 로직에 대해 검증이 가능하니 컴파일 오류로 잡아낼 수 있어서 그렇게 했지만 힙메모리에 올라가는 인스턴스 변수들은 절차적인 프로그래밍이 아닌 객체지향 프로그래밍 언어인 자바에선 로직에 대해 검증이 안되니 강제 초기화한 것이 아닐까염.. 객체들은 어쩔수없이 null로 초기화하구...

2013-06-29 13:02

@자바지기 음 아직 이사짐 정리하느라 자세히는 못봤는데 일단 난 원론적인 질문을 몇가지 던져 보려고 한다. (일단 이 답에 대한 생각이 있다면 어느정도 이해가 되지 않을까?)

  1. 변수는 왜 초기화 되어야 하는가?
  2. compiler는 왜 필요한가?
  3. compiler는 어떤식으로 소스코드를 이해하는가?(여기서는 Java)

이 질문들이 답을 보여주지 않을까?

2013-06-29 14:18

@자바지기 필드를 초기화하지 않더라도 컴파일 에러를 발생시키지 않는게 아니라 final이 아닌 필드는 선언만 해도 암시적으로 기본값이 할당된다고 하는 게 맞겠죠. 로컬 변수와 달리 클래스의 필드가 초기화되었는지 알 수 없는 이유는 로컬 스코프에서는 구문 하나하나가 실행되는 순서를 컴파일러가 알 수 있지만 필드의 경우, 필드의 값을 읽는 메서드가 있고 값을 할당하는 메서드가 있을 때, 어느 메서드가 먼저 실행될지, 그래서 필드 값을 읽기 전에 할당이 먼저 될지 확실하게 알 수 없기 때문입니다. 이는 정적 분석으로도 알 수 없죠.

저는 자바의 경우 굳이 "선언"과 "초기화"로 구분할 필요가 없으므로 변수를 "정의"할 때 값도 명시적으로 할당하는 게 모호한 상황을 피할 수 있다고 생각합니다.

2013-07-04 22:57

언어 명세서에 번역단위(Compilation Unit, 또는 Translation Unit)가 정의되어 있습니다. 자바에서는 번역단위가 하나의 파일(클래스)로 볼 수 있습니다. Data 들은 Data Flow Analysis 기법(최적화기법에서)을 통해서 define-use 의 흐름을 찾을 수 있습니다. 번역 단위 내에서 클래스의 필드는 그 data 흐름이 명확하지 못한(파일간, 전체시스템의 흐름을 알수 없어서) 반면, 지역변수는 그 data 흐름이 명확합니다.

2013-08-07 09:17

내부적으로 이렇게 돌아가기에 그 결과가 나온 게 아니라, 결과를 위해서 내부적으로 그렇게 돌아가는 것입니다. 다른 분도 언급을 했지만, 로마에 가면 로마 법을 따라는 말처럼, 자바 언어의 스펙이 그런 것이니 너무 깊게 생각하는 것은 정신 건강에 해로울 수 있습니다. ^^

의견 추가하기

연관태그

← 목록으로