변수를 선언하고 처음으로 값을 저장하는 것을 '변수의 초기화'라고 한다.
멤버변수는 초기화를 하지 않아도 자동적으로 변수의 자료형에 맞는 기본값으로 초기화가 이루어지지만, 지역변수는 사용하기 전에 반드시 초기화해야 한다.
class InitTest {
int x; // 인스턴스 변수
int y = x; // 인스턴스 변수
void method1() {
int i; // 지역변수
int j = i; // 에러. 지역변수를 초기화하지 않고 사용
}
}
위의 코드에서 x, y는 인스턴스 변수이고, i, j는 지역변수이다. 그 중 x와 i는 선언만 하고 초기화를 하지 않았다. 그리고 y를 초기화 하는데 x를 사용했고, j를 초기화 하는데 i를 사용했다.
인스턴스 변수 x는 초기화를 해주지 않아도 자동적으로 int형의 기본값인 0으로 초기화되므로, 'int y = x;'와 같이 할 수 있다. x의 값이 0이므로 y 역시 0이 저장된다.
하지만, method1()의 지역변수 i는 자동적으로 초기화되지 않으므로, 초기화 되지 않은 상태에서 변수 j를 초기화 하는데 사용될 수 없다. 컴파일하면, 에러가 발생한다.
1. 명시적 초기화(explicit initialization)
2. 생성자(constructor)
3. 초기화 블럭(initialization block)
- 인스턴스 초기화 블럭: 인스턴스변수를 초기화 하는데 사용
- 클래스 초기화 블럭: 클래스변수를 초기화 하는데 사용
변수를 선언과 동시에 초기화하는 것을 명시적 초기화라 한다.
class Car {
int door = 4; // 기본형 변수의 초기화
Engine e = new Engine(); // 참조형 변수의 초기화
//...
}
명시적 초기화는 가장 기본적이면서도 간단한 초기화 방법이므로 여러 초기화 방법 중에서 가장 우선적으로 고려되어야 한다.
명시적 초기화가 간다하고 명료하긴 하지만, 보다 복잡한 초기화 작업이 필요할 때는 '초기화 블럭' 또는 생성자를 사용해야 한다.
클래스 초기화 블럭 : 클래스변수의 복잡한 초기화에 사용된다.
인스턴스 초기화 블럭 : 인스턴스변수의 복잡한 초기화에 사용된다.
초기화 블럭을 작성하려면, 인스턴스 초기화 블럭은 단순히 클래스 내에 블럭{}을 만들고 그 안에 코드를 작성하기만 하면 된다. 그리고 클래스 초기화 블럭은 인스턴스 초기화 블럭 앞에 단순히 static을 덧붙이기만 하면 된다.
초기화 블럭 내에는 메서드 내에서와 같이 조건문, 반복문, 예외처리구문 등을 자유롭게 사용할 수 있으므로, 초기화 작업이 복잡하여 명시적 초기화만으로는 부족한 경우 초기화 블럭을 사용한다.
class InitBlock {
static { /* 클래스 초기화 블럭입니다. */ }
{ /* 인스턴스 초기화 블럭입니다. */ }
//...
}
클래스 초기화 블럭은 클래스가 메모리에 처음 로딩될 때 한번만 수행되며, 인스턴스 초기화 블럭은 생성자와 같이 인스턴스를 생성할 때 마다 수행된다.
(클래스가 처음 로딩될 때 클래스변수들이 자동적으로 메모리에 만들어지고, 곧바로 클래스 초기화블럭이 클래스변수들을 초기화하게 되는 것이다.)
인스턴스 변수의 초기화는 주로 생성자를 사용하고, 인스턴스 초기화 블럭은 모든 생성자에서 공통으로 수행돼야 하는 코드를 넣는데 사용된다.
public class BlockTest {
static {
System.out.println("static { }"); // 클래스 초기화 블럭
}
{
System.out.println("{ }"); // 인스턴스 초기화 블럭
}
public BlockTest() {
System.out.println("생성자");
}
public static void main(String[] args) {
System.out.println("BlockTest bt = new BlockTest(); ");
BlockTest bt = new BlockTest();
System.out.println("BlockTest bt = new BlockTest(); ");
BlockTest bt2 = new BlockTest();
}
}
예제가 실행되면서 BlockTest가 메모리에 로딩될 때, 클래스 초기화 블럭이 가장 먼저 수행되어 'static {}'이 화면에 출력된다. 그 다음에 main메서드가 수행되어 BlockTest인스턴스가 생성되면서 인스턴스 초기화 블럭이 먼저 수행되고, 끝으로 생성자가 수행된다.
위의 실행결과에서도 알 수 있듯이 클래스 초기화 블럭은 처음 메모리에 로딩될 때 한번만 수행되었지만, 인스턴스 초기화 블럭은 인스턴스가 생성될 때 마다 수행되었다.
클래스변수의 초기화시점 : 클래스가 처음 로딩될 때 단 한번 초기화 된다.
인스턴스변수의 초기화시점 : 인스턴스가 생성될 때마다 각 인스턴스별로 초기화가 이루어진다.
클래스변수의 초기화순서: 기본값 -> 명시적 초기화 -> 클래스 초기화 블럭
인스턴스변수의 초기화순서: 기본값 -> 명시적 초기화 -> 인스턴스 초기화 블럭 -> 생성자
프로그램 실행도중 클래스에 대한 정보가 요구될 때, 클래스는 메모리에 로딩된다.
예를 들면, 클래스 멤버를 사용했을 때, 인스턴스를 생성할 때 등이 이에 해당한다.
하지만, 해당 클래스가 이미 메모리에 로딩되어 있다면, 또다시 로딩하지 않는다. 물론 초기화도 다시 수행되지 않는다.
class InitTest {
static int cv = 1; // 명시적 초기화
int iv = 1;
static { cv = 2; } // 클래스 초기화 블럭
{ iv = 2; } // 인스턴스 초기화 블럭
InitTest() {
iv = 3;
}
}
위의 예시에서 'new InitTest();'와 같이 하여 인스턴스를 생성했을 때, cv와 iv가 초기화되어가는 과정을 살펴보겠다.
class Product {
static int count = 0; //생성된 인스턴스의 수를 저장하기 위한 변수
int serialNo;
{
++count;
serialNo = count; // Product인스턴스가 생성될 때마다 count의 값을 1씩 증가시켜서 serialNo에 저장한다.
}
public Product() {} // 기본생성자, 생략 가능
}
public class ProductTest {
public static void main(String[] args) {
Product p1 = new Product();
Product p2 = new Product();
Product p3 = new Product();
System.out.println("p1제품번호(serial no)는" +p1.serialNo);
System.out.println("p2제품번호(serial no)는" +p2.serialNo);
System.out.println("p3제품번호(serial no)는" +p3.serialNo);
System.out.println("생산된 제품의 수는 모두 " + Product.count + "개 입니다.");
}
}
Product 클래스의 인스턴스를 생성할 때마다 인스턴스 블럭이 수행되어,클래스변수 count의 값을 1증가시킨 다음, 그 값을 인스턴스변수 serialNo에 저장한다. 이렇게 함으로써 새로 생성되는 인스턴스는 이전에 생성된 인스턴스보다 1이 증가된 serialNo을 갖게 된다.
참고
자바의정석(저자: 남궁성)
드디어 객체지향프로그래밍 파트1이 끝났다. 해당 부분을 공부하고 정리해서 포스팅까지 했지만, 여전히 헷갈리는 용어와 개념들이 있다. 부디 앞으로 더 많은 내용을 배우고, 복습해가면서 개념들이 익숙해지고 완전히 자리잡길 바란다. 공부를 할수록 내용은 더 어려워지지만, '객체지향'이라는 것이 무엇인지에 대한 감이 생기기 시작한다. 자바는 정말 매력적인 언어다.
다음은 객체지향프로그래밍 파트2에 들어가면서 상속에 대해 공부하겠다.