생성자 (Constructor)
생성자는 객체가 메모리에 생성될 때 자동으로 호출되는 특수한 메서드입니다. 주요 목적은 객체의 초기 상태를 설정하는 것이며, 객체 하나당 정확히 한 번만 실행됩니다. 반면 일반적인 메서드는 객체가 존재하는 동안 여러 번 호출될 수 있습니다.
public class UserAccount {
// 생성자
public UserAccount() {
System.out.println("UserAccount 객체 초기화 완료");
}
}
인스턴스 초기화 블록 (Instance Initialization Block)
인스턴스 초기화 블록은 클래스 내부에 중괄호 {}만으로 정의되는 코드 블록입니다. 이 블록은 새로운 객체가 생성될 때마다 실행되며, 생성자보다 먼저 호출됩니다.
{
// 인스턴스 초기화 블록
System.out.println("공통 초기화 로직 실행");
}
인스턴스 초기화 블록의 핵심 특징은 다음과 같습니다:
- 모든 생성자에 공통으로 포함되어야 하는 초기화 코드를 중복 없이 작성할 때 유용합니다.
- 객체 생성 시점에 실행되므로 클래스 이름으로는 직접 호출할 수 없습니다.
- 여러 생성자가 오버로딩되어 있더라도, 어떤 생성자가 호출되든 인스턴스 초기화 블록은 반드시 먼저 실행되어 객체의 공통된 특성을 초기화합니다.
정적 초기화 블록 (Static Initialization Block)
정적 초기화 블록은 static {} 키워드로 선언됩니다. 이 블록은 클래스가 JVM에 처음 로딩될 때 단 한 번만 실행되며, main 메서드보다 먼저 실행됩니다.
static {
// 정적 초기화 블록
System.out.println("클래스 로딩 시 1회 실행");
}
정적 초기화 블록의 주요 특성은 다음과 같습니다:
- 클래스 단위의 초기화를 담당하며, 객체 생성과는 무관하게 클래스 로딩 시점에 실행됩니다.
- 주로 정적 변수의 복잡한 초기화나 외부 리소스 로딩, 드라이버 등록 등에 사용됩니다.
- 블록 내부에서 선언된 변수는 해당 블록 내에서만 유효한 지역 변수입니다.
- 하나의 클래스 내에 여러 개의 정적 초기화 블록을 정의할 수 있으며, 코드에 나타난 순서대로 실행됩니다.
public class AppConfig {
static int maxRetries = 3;
static {
maxRetries *= 2;
}
public static void main(String[] args) {
System.out.println("최대 재시도 횟수: " + maxRetries);
}
static {
maxRetries += 4;
}
}
// 실행 결과:
// 최대 재시도 횟수: 10
단일 클래스의 초기화 순서
상속이 없는 단일 클래스에서 객체를 생성할 때의 초기화 순서는 다음과 같습니다:
- 정적 변수 및 정적 초기화 블록 (클래스 로딩 시 1회)
- 인스턴스 변수 및 인스턴스 초기화 블록 (객체 생성 시마다)
- 생성자 (객체 생성 시마다)
public class ServerNode {
public static String staticProp = "정적 변수";
public String instanceProp = "인스턴스 변수";
static {
System.out.println(staticProp);
System.out.println("정적 초기화 블록 실행");
}
{
System.out.println(instanceProp);
System.out.println("인스턴스 초기화 블록 실행");
}
public ServerNode() {
System.out.println("생성자 실행");
}
public static void main(String[] args) {
new ServerNode();
}
}
출력 결과:
정적 변수
정적 초기화 블록 실행
인스턴스 변수
인스턴스 초기화 블록 실행
생성자 실행
상속 관계에서의 초기화 순서
클래스 간 상속 관계가 형성되어 있을 경우, 초기화 순서는 부모 클래스에서 자식 클래스로 내려가는 계층 구조를 따릅니다.
- 부모 클래스의 정적 변수 및 정적 초기화 블록
- 자식 클래스의 정적 변수 및 정적 초기화 블록
- 부모 클래스의 인스턴스 변수, 인스턴스 초기화 블록, 생성자
- 자식 클래스의 인스턴스 변수, 인스턴스 초기화 블록, 생성자
class BaseService {
public static String baseStatic = "부모 정적 변수";
public String baseInstance = "부모 인스턴스 변수";
protected int status = 100;
static {
System.out.println(baseStatic);
System.out.println("부모 정적 초기화 블록");
}
{
System.out.println(baseInstance);
System.out.println("부모 인스턴스 초기화 블록");
}
public BaseService() {
System.out.println("부모 생성자");
System.out.println("status = " + status);
status = 200;
}
}
public class PaymentService extends BaseService {
public static String payStatic = "자식 정적 변수";
public String payInstance = "자식 인스턴스 변수";
static {
System.out.println(payStatic);
System.out.println("자식 정적 초기화 블록");
}
{
System.out.println(payInstance);
System.out.println("자식 인스턴스 초기화 블록");
}
public PaymentService() {
System.out.println("자식 생성자");
System.out.println("status = " + status);
}
public static void main(String[] args) {
System.out.println("--- main 메서드 시작 ---");
new PaymentService();
}
}
출력 결과:
부모 정적 변수
부모 정적 초기화 블록
자식 정적 변수
자식 정적 초기화 블록
--- main 메서드 시작 ---
부모 인스턴스 변수
부모 인스턴스 초기화 블록
부모 생성자
status = 100
자식 인스턴스 변수
자식 인스턴스 초기화 블록
자식 생성자
status = 200
위 결과에서 알 수 있듯이, 자식 클래스의 정적 초기화는 부모 클래스의 인스턴스 초기화나 생성자 호출보다 훨씬 이전에 완료됩니다. 또한, 각 카테고리(정적, 인스턴스) 내에서의 초기화 순서는 소스 코드상에 선언된 순서를 엄격히 따릅니다.
JVM 클래스 로딩 및 초기화 내부 동작 분석
- 클래스 로딩 및 계층 탐색:
PaymentService.main()메서드가 호출되면, JVM 클래스 로더는 컴파일된PaymentService.class파일을 메모리에 로드합니다. 이 과정에서 부모 클래스인BaseService가 존재함을 감지하고, 부모 클래스 또한 함께 로드합니다. 이는 객체 생성 여부와 무관하게 클래스 로딩 시점에 반드시 발생합니다. - 정적 초기화의 계층적 실행: 최상위 부모 클래스부터 자식 클래스 방향으로 정적(static) 초기화가 순차적으로 진행됩니다. 자식 클래스의 정적 초기화가 부모 클래스의 정적 멤버에 의존할 수 있기 때문에 이 순서는 매우 중요합니다.
- main 메서드 실행 및 객체 생성: 모든 관련 클래스의 로딩과 정적 초기화가 완료된 후,
main메서드의 본문이 실행되며new PaymentService()를 통한 객체 생성이 시작됩니다. - 부모 클래스의 인스턴스 초기화: 자식 클래스의 생성자가 호출되면, 암묵적으로 또는 명시적으로 부모 클래스의 생성자(
super())가 먼저 호출됩니다. 부모 클래스의 인스턴스 변수 초기화, 인스턴스 초기화 블록, 생성자 본문이 순서대로 실행됩니다. - 자식 클래스의 인스턴스 초기화: 부모 클래스의 초기화가 완료되면, 이어서 자식 클래스의 인스턴스 변수 초기화, 인스턴스 초기화 블록, 그리고 생성자 본문이 실행되며 객체 생성이 마무리됩니다.