어떤 공부를 하더라도 항상 기본이 중요하다고 생각합니다. 기본의 탄탄함이 앞으로 배워나갈 심화된 내용을 다룰때 더욱 넓은 시야를 가지고 바라보게 하는것 같네요.
이 포스팅에서는 Java의 클래스 안의 변수인 멤버변수를 초기화하는 방법에 대해서 다룹니다.
참고 서적은 자바의 정석(6.변수의 초기화, 300p~307p)입니다. 이 글의 모든 예시코드는 자바의 정석에서 가져온 것입니다.
변수의 초기화
변수의 초기화란 변수의 선언 이후에 선언한 변수에 처음으로 값을 저장하는 것을 의미합니다.
멤버변수는 초기화를 하지 않아도 기본값이 적용되어서 문제가 없지만, 지역변수의 경우는 사용하기 전에는 반드시 초기화를 해주어야 합니다.
class InitTest {
int x; //인스턴스변수
int y = x; //인스턴스변수
void method1() {
int i; //지역변수
int j = i; //에러, 지역변수를 초기화하지 않고 사용
}
}
위의 예시 코드에서 x와 y는 인스턴스변수이고, i와 j는 method1 메서드의 지역변수입니다. x와 i는 선언한 이후에 초기화를 하지 않았고, y와 j에는 초기화되지 않은 x와 i가 초기화하는데 사용되었습니다. 인스턴스변수는 초기화를 해주지 않아도 자동적으로 기본 값으로 초기화가 됩니다. 따라서 int 타입인 x는 기본값인 0으로 초기화되고, y는 x에 저장된 0으로 초기화되며 오류 또한 발생하지 않습니다. 반면에 지역변수인 i는 자동으로 초기화되지 않기 때문에 초기화되지 않은 변수인 i를 j에 저장할 때 에러가 발생합니다.
이를 통해서 멤버변수는 초기화해주지 않아도 되지만 지역변수의 경우 초기화를 반드시 해야한다는 점을 알 수 있습니다.
각 타입의 기본값
아래는 각 타입의 기본값입니다.
자료형 | 기본값 |
---|---|
boolean | false |
char | ’\u0000’ |
byte, short, int | 0 |
long | 0L |
float | 0.0f |
double | 0.0d 또는 0.0 |
참조형변수 | null |
명시적 초기화(Explicit Initialization)
변수를 선언과 동시에 초기화하는 것을 명시적 초기화라고 합니다. 가장 기본적이면서도 간단하기 때문에 여러 초기화 방법중에서 가장 우선적으로 고려되어야 한다고 합니다.
class Car {
int door=4; //기본형(primitive type) 변수의 초기화
Engine e = new Engine(); //참조형(reference type) 변수의 초기화
// ...
}
간단하고 명료한 방법이지만 이보다 복잡한 초기화 과정이 필요할 때는 초기화 블럭(initialization block) 또는 생성자(constructor)를 사용해야합니다.
초기화 블럭(Initialization Block)
초기화 블럭에는 클래스 초기화 블럭과 인스턴스 초기화 블럭 두 가지가 있습니다. 클래스 초기화 블럭은 클래스 변수를 초기화할 때 사용하고, 인스턴스 초기화 블럭은 인스턴스 변수의 초기화에 사용합니다.
초기화 블럭 작성법
초기화 블럭을 작성하는 방법은 매우 간단합니다. { }을 작성하고 그 안에 코드를 작성하기만 하면 됩니다.
class InitBlock {
static { /* 클래스 초기화 블럭입니다.*/ }
{ /* 인스턴스 초기화 블럭입니다. */ }
}
위의 코드에서 보시다시피 클래스 초기화 블럭과 인스턴스 초기화 블럭의 차이점은 static
의 유무입니다.
초기화 블럭 내에는 메서드 내에서와 같이 조건문, 반복문, 예외처리구문 등을 자유롭게 사용할 수 있어서
초기화 작업이 명시적 초기화만으로는 부족할 때 초기화 블럭을 사용한다고 합니다.
클래스 초기화 블럭은 클래스가 메모리에 처음 로딩될 때 한번만 수행되고, 인스턴스 초기화 블럭은 생성자와 같이 인스턴스가 생성될 때마다 수행됩니다.
인스턴스 변수의 초기화에는 주로 생성자가 사용되며, 인스턴스 초기화 블럭은 모든 생성자에서 공통으로 수행해야하는 코드를 넣는데 사용됩니다. 아래는 해당 내용에 대한 예시 코드입니다.
class Car {
// ...
Car() {
count++;
serialNo = count;
color = "White";
gearType = "Auto";
}
Car(String color, String gearType) {
count++;
serialNo = count;
this.color = color;
this.gearType = gearType;
}
// ...
}
위 코드에서 각각의 생성자의 첫 두줄이 반복되는 것을 보실 수 있습니다. 인스턴스 초기화 블럭을 사용해서 중복되는 해당 부분을 따로 빼서 아래처럼 작성할 수 있습니다.
class Car {
// ...
{ // 인스턴스 초기화 블럭
count++;
serialNo = count;
}
Car() {
color = "White";
gearType = "Auto";
}
Car(String color, String gearType) {
this.color = color;
this.gearType = gearType;
}
// ...
}
초기화 블럭의 호출 시점
아래 예시코드를 통해서 클래스 초기화 블럭과 인스턴스 초기화 블럭이 호출되는 시점을 알아봅시다.
class BlockTest {
static {
System.out.println("static { }");
}
{
System.out.println("{ }");
}
public BlockTest() {
System.out.println("constructor");
}
public static void main(String[] args) {
System.out.println("BlockTest bt = new BlockTest();");
BlockTest bt = new BlockTest();
System.out.println("BlockTest bt2 = new BlockTest();");
BlockTest bt2 = new BlockTest();
}
}
위 코드의 실행 결과는 아래와 같습니다.
static { }
BlockTest bt = new BlockTest();
{ }
constructor
BlockTest bt2 = new BlockTest();
{ }
constructor
순서대로 보시면
- 맨 처음 프로그램이 실행되고 메모리에 BlockTest 클래스가 로딩될 때, 클래스 초기화 블럭이 가장 먼저 호출이 되었습니다.
- 이후 BlockTest 클래스의 생성자 함수를 호출하였습니다.
- 인스턴스 초기화 블럭이 수행되었습니다.
- 생성자 함수가 수행되었습니다.
위 코드를 통해서 수행 순서는 클래스 초기화 블럭 -> 인스턴스 초기화 블럭 -> 블럭 생성자 함수
순서인 것을 알 수 있습니다.
또한 클래스 초기화 블럭은 맨 처음 한번만 수행된 반면에 인스턴스 초기화 블럭은 인스턴스가 생성될 때마다 수행된 것을 알 수 있습니다.
명시적 초기화까지 포함해서 정리하자면 아래와 같습니다.
클래스 변수의 초기화 시점: 클래스가 처음 로딩될 때 단 한번 초기화됩니다. 인스턴스 변수의 초기화 시점: 인스턴스가 생성될 때마다 각 인스턴스 별로 초기화가 이루어집니다.
> 클래스 변수의 초기화 순서: 기본값 -> 명시적초기화 -> 클래스 초기화 블럭 인스턴스 변수의 초기화 순서: 기본값 -> 명시적초기화 -> 인스턴스 초기화 블럭 -> 생성자
클래스가 로딩될 때는 클래스에 대한 정보가 필요할 경우입니다. 예를 들면, 인스턴스가 생성할 때, 클래스 멤버를 사용했을 때입니다. 하지만 해당 클래스가 메모리에 이미 로딩되어 있다면 다시 로딩하지 않고, 초기화 또한 다시 수행하지 않습니다.
Comments