1. 초기화 블록이란?
Java에는 두 가지 초기화 블록이 있습니다:
- 인스턴스(비정적) 초기화 블록 — 새 객체를 만들 때마다 실행되며, 필드 초기화 직후이자 생성자 호출 이전에 실행됩니다.
- 정적 초기화 블록 — 클래스가 메모리에 로드될 때 한 번만 실행됩니다(첫 번째 객체 생성 또는 정적 필드/메서드에 접근하기 전).
인스턴스 초기화 블록
클래스 본문에 단순히 작성하며, static 같은 키워드는 사용하지 않습니다:
public class User {
private String name;
// 인스턴스 초기화 블록
{
System.out.println("인스턴스 초기화 블록 실행!");
name = "기본 이름";
}
public User() {
System.out.println("생성자 실행!");
}
}
정적 초기화 블록
static 키워드로 선언합니다:
public class Config {
public static String appName;
static {
System.out.println("정적 초기화 블록 실행!");
appName = "나의 슈퍼 애플리케이션";
}
}
2. 초기화 순서: 누가 먼저일까?
Java에서 클래스 요소의 초기화 순서는 임의로 정해지는 것이 아니라 엄격히 정의되어 있습니다. IKEA 설명서 없이 가구를 조립해 본 적이 있다면 왜 순서가 중요한지 알 겁니다. 단계를 뒤섞으면 옷장 대신 예술 작품이 나올 수도 있으니까요.
초기화 순서:
- 정적 필드와 정적 초기화 블록 — 클래스에 선언된 순서대로. 클래스가 로드될 때 한 번만 실행됩니다.
- 인스턴스 필드와 인스턴스 초기화 블록 — 선언된 순서대로, 객체를 생성할 때마다 실행됩니다.
- 생성자 — 모든 인스턴스 초기화가 끝난 뒤 실행됩니다.
도식으로 보기
+-------------------------------+
| JVM에서 클래스 로드 |
+-------------------------------+
| 1. 정적 필드 |
| 2. 정적 초기화 블록 |
| ↓ |
| 객체 생성 |
| ↓ |
| 3. 인스턴스 필드 |
| 4. 인스턴스 초기화 블록 |
| 5. 생성자 |
+-------------------------------+
출력 순서 예시
모든 것이 어떤 순서로 일어나는지 보여 주는 클래스를 작성해 보겠습니다:
public class Demo {
static String staticField = print("1. static 필드");
static {
print("2. static 블록");
}
String field = print("3. 인스턴스 필드");
{
print("4. 인스턴스 초기화 블록");
}
public Demo() {
print("5. 생성자");
}
static String print(String msg) {
System.out.println(msg);
return msg;
}
public static void main(String[] args) {
System.out.println("첫 번째 Demo 객체를 생성합니다:");
Demo d1 = new Demo();
System.out.println("\n두 번째 Demo 객체를 생성합니다:");
Demo d2 = new Demo();
}
}
화면 출력은?
1. static 필드
2. static 블록
첫 번째 Demo 객체를 생성합니다:
3. 인스턴스 필드
4. 인스턴스 초기화 블록
5. 생성자
두 번째 Demo 객체를 생성합니다:
3. 인스턴스 필드
4. 인스턴스 초기화 블록
5. 생성자
참고: 정적(static) 부분은 클래스에 처음 접근할 때 단 한 번만 실행됩니다. static이 아닌 모든 것은 객체를 만들 때마다 실행됩니다.
3. 코드 예시: 초기화 블록을 언제 쓰나
생성자만으로는 부족할 때
여러 생성자가 있고, 그들 모두에 공통으로 필요한 초기화가 있다면 인스턴스 초기화 블록으로 빼는 것이 편리합니다. 각 생성자에 같은 코드를 복사하지 않아도 됩니다:
public class Person {
private String id;
private String name;
{
// 이 코드는 어떤 생성자보다 먼저 실행됩니다
id = java.util.UUID.randomUUID().toString();
System.out.println("고유 id 생성: " + id);
}
public Person() {
System.out.println("Person() 매개변수 없음");
}
public Person(String name) {
this.name = name;
System.out.println("Person(String name)");
}
}
결과: 어떤 Person 객체를 생성하든 먼저 id가 생성되고, 그다음 해당 생성자가 실행됩니다.
복잡한 정적 데이터 초기화
정적 블록은 파일에서 구성 읽기, 컬렉션 생성, 데이터베이스 연결 등 “무거운” 또는 복잡한 정적 필드를 초기화할 때 자주 사용됩니다.
public class Settings {
public static final java.util.Map<String, String> DEFAULTS;
static {
DEFAULTS = new java.util.HashMap<>();
DEFAULTS.put("theme", "light");
DEFAULTS.put("language", "ru");
System.out.println("Settings 정적 블록: 기본 설정");
}
}
4. 유용한 팁과 주의점
초기화 블록을 사용할 때
다음과 같은 경우에 권장
- 모든 생성자에 공통으로 필요한 초기화.
- 단순 대입으로 표현할 수 없는 복잡한 정적 데이터.
- 정적 자원 초기화(예: 애플리케이션 시작 시 구성 파일 읽기).
다음과 같은 경우에는 사용하지 말 것
- 단순 대입이나 생성자로 충분하다면 그것을 사용하세요.
- 비즈니스 로직을 초기화 블록에 “숨기지” 마세요 — 코드 가독성과 유지보수가 나빠집니다.
- 초기화가 생성자 매개변수에 의존한다면 생성자에서 처리하세요.
초기화 블록을 남용하지 마세요
초기화 블록은 강력하지만 자주 필요한 도구는 아닙니다. 대부분의 경우 단순 대입이나 생성자로 충분합니다. 초기화 블록이 너무 많아지면 코드는 읽기 어렵고 유지보수하기 어려워집니다.
생성자 매개변수에 의존하는 로직을 인스턴스 블록에 넣지 마세요
인스턴스 초기화 블록은 생성자보다 먼저 실행되므로 생성자 매개변수를 사용할 수 없습니다. 매개변수에 기반한 초기화가 필요하다면 생성자에서 처리하세요.
정적 블록과 상속
정적 초기화 블록은 상속되지 않습니다. 각 클래스마다 자신만의 정적 블록이 있습니다. 자식 클래스를 로드할 때 기본 클래스의 정적 블록이 먼저, 그다음 자식 클래스의 정적 블록이 실행됩니다.
5. 초기화 블록에서 흔한 실수
실수 1: 인스턴스 블록에서 생성자 매개변수를 볼 수 있다고 기대한다.
많은 초보자가 인스턴스 블록에서 생성자 매개변수를 사용하려 하지만 컴파일 오류나 예상치 못한 결과를 얻습니다. 기억하세요: 인스턴스 블록은 생성자 이전에 실행되므로 매개변수가 아직 없습니다.
실수 2: 초기화 블록에 과도한 로직을 넣는다.
초기화 블록에 복잡한 로직이 들어가면 코드가 난해해집니다. 주요 작업은 생성자나 별도의 메서드에서 처리하는 것이 좋습니다.
실수 3: 여러 정적 블록의 선언 순서를 간과한다.
클래스에 정적 블록이 여러 개라면, 정적 필드와 함께 코드에 선언된 순서대로 실행됩니다. 한 블록이 다른 블록의 결과에 의존하면 예상치 못한 결과를 낳을 수 있습니다.
실수 4: 객체를 만들 때마다 정적 블록이 실행된다고 기대한다.
static 블록은 클래스가 로드될 때 단 한 번만 실행됩니다. 반복 초기화를 기대해도 그렇게 되지 않습니다.
실수 5: 정적 블록에서 인스턴스 필드에 접근하려 한다.
정적 블록에서는 정적 변수와 메서드만 사용할 수 있습니다. 인스턴스(비정적) 필드에 접근하려 하면 컴파일 오류가 발생합니다.
GO TO FULL VERSION