[Java] 생성자 초기화 순서 정리: 5단계에서 7단계로 (feat. JEP 513)
이것이 자바다 책을 보면서 조금 애매한 설명이 있어서 추가로 공부하게 되었다.
[레퍼런스] Chapter 12. Execution | Oracle Java SE 21
흔히 우리가 알던 자바 버전에서는 객체를 생성하게 되면 다음과 같은 순서로 진행된다.
- 메모리 공간 할당
- 모든 인스턴스 변수(상위 클래스 포함) 기본값으로 초기화
- 새 객체를 초기화 하기 위해 생성자 처리 5단계 진행
- 예외 없이 정상적으로 생성자 처리가 끝나면 새로 생성된 객체에 대한 참조가 결과로 반환
생성자 체이닝 (Constructor Chaining)
생성자가 다른 생성자를 호출하는 메커니즘
두 가지 형태가 있다.
super(...)→ 부모 클래스 생성자 호출this(...)→ 같은 클래스의 다른 생성자 호출
생성자 처리 5단계
- 생성자 인자를 생성자를 호출하기 위해 매개변수 변수들에 할당한다. (그냥 함수 호출하기 위한 과정이라고 보면 된다.)
- 생성자 첫 줄이
this(...)면, 해당 생성자에 대해 step 1~5를 재귀적으로 먼저 처리한다. 정상적으로 실행이 끝나면 step 5로 이동한다.- 왜 바로 step 5로 가냐? → 같은 클래스에서 step 4의 initializers가 중복 실행되지 않도록 (
super를 호출하는this체인의 맨 끝에서 step 4가 한 번 실행될 수 있게)
- 왜 바로 step 5로 가냐? → 같은 클래스에서 step 4의 initializers가 중복 실행되지 않도록 (
this(...)가 아니면,Object가 아닌 한super(...)를 명시/암묵적으로 호출하고, 그 슈퍼 생성자 호출을 재귀적으로 처리한다. 끝나면 step 4 진행.- 생성자 첫 줄에
super()가 없더라도, 컴파일러가 자동으로 넣어준다. - 따라서 부모 생성자가 항상 먼저 실행됨
Object클래스는 부모가 없으므로 재귀 종료 시점이 된다.
- 생성자 첫 줄에
- 현재 클래스에 적힌 필드 초기값 설정(instance variable initializer)과 인스턴스 초기화 블록(instance initializer)을 소스 코드 순서대로 실행한다.
- 생성자 본문의 나머지(
super/this호출 이후)를 실행한다.
한 줄로 표현하자면, 재귀 호출을 통해 최상위 클래스(Object)까지 올라갔다가 다시 원래 클래스로 내려오면서 필드를 초기화하고 생성자 본문을 실행한다고 보면 된다.
이 과정에서 예외가 한 번이라도 터지면 객체 생성은 실패한다.
Java 25: Flexible Constructor Bodies (JEP 513)
JEP 513: Flexible Constructor Bodies
그런데 Java 25부터 생성자 본문에서 명시적 생성자 호출(super(...) 또는 this(...)) 앞에 문장을 쓸 수 있게 변경되었다.
이제 생성자 본문이 두 구간으로 나뉜다.
- 프롤로그(Prologue) : 생성자 호출 전에 오는 문장들
- 에필로그(Epilogue) : 생성자 호출 후에 오는 문장들
목표
- 상위 클래스 생성자를 호출하기 전에 인자를 쉽게 검증할 수 있다.
- 새 객체의 상태를 다른 코드에서 사용되기 전에 초기화할 수 있다.
- 생성자들끼리 서로 상호 작용해서 생성 과정을 재구상할 수 있다.
Early construction contexts
명시적 생성자 호출에서 인자들은 정적 컨텍스트(static context)에 있다고 한다. 즉, 인스턴스가 존재하지 않은 정적 메서드 안의 코드처럼 취급된다. 이 개념을 수정하지 않는 대신 early consturction context를 만들었다고 한다. 여기엔 인자 + 프롤로그가 포함된다.
여기선 명시적이든 암시적이든 this 를 사용해서 현재 인스턴스를 참조, 필드 접근, 메서드 호출을 할 수 없다. 단, 현재 클래스에 선언된 필드 중에서 초기화식이 없는 필드에 대해선 단순 대입문은 허용된다고 한다.
class X {
int i;
String s = "hello";
X() {
System.out.print(this); // 오류 - 현재 인스턴스를 명시적으로 참조함
var x = this.i; // 오류 - 현재 인스턴스의 필드를 명시적으로 참조함
this.hashCode(); // 오류 - 현재 인스턴스의 메서드를 명시적으로 참조함
var y = i; // 오류 - 현재 인스턴스의 필드를 암시적으로 참조함
hashCode(); // 오류 - 현재 인스턴스의 메서드를 암시적으로 참조함
i = 42; // OK - 초기화식이 없는 필드에 대한 대입
s = "goodbye"; // 오류 - 초기화식이 있는 필드에 대한 대입
super();
}
}
추가로, 슈퍼 클래스에 대한 필드나 메서드 호출도 금지된다.
class Y {
int i;
void m() { ... }
}
class Z extends Y {
Z() {
var x = super.i; // 오류
super.m(); // 오류
super();
}
}
바뀐 생성자 처리 7단계
[레퍼런스] Chapter 12. Execution | Oracle Java SE 25
JEP 513으로 인해 JLS 12.5도 바뀌었다.
- 생성자 인자를 생성자를 호출하기 위해 매개변수 변수들에 할당한다.
- 이 생성자가 명시적 생성자 호출을 포함하지 않으면 step 5로 이동한다.
- 생성자 본문에 프롤로그에 해당하는
BlockStatements가 있으면 실행한다. - 명시적 생성자 호출(
this/super)에 대해 step 1~7을 재귀적으로 처리한다. 정상적으로 실행이 끝나면 다음과 같이 행동한다.this였으면 → step 7으로 이동super였으면 → step 6으로 이동
- 현재 생성자가
Object가 아닌 클래스의 생성자면 이 생성자는 인자 없는 슈퍼클래스 생성자에 대한 암묵적 호출을 가지고 있고, 이 역시 step 1~7을 재귀적으로 처리한다. - 현재 클래스에 적힌 필드 초기값(instance variable initializer)과 인스턴스 초기화 블록(instance initializer)을 소스 코드 순서대로 실행한다.
- 이 생성자의 에필로그에 해당하는
BlockStatements가 있으면 실행한다.
정리
간단하게 다음과 같이 표현하면 될 것 같다.
- 생성자 처리 5단계: (super/this 호출) → (클래스당 한 번 initializer) → 생성자 남은 부분 실행
- 생성자 처리 7단계: prologue → (super/this 호출) → (클래스당 한 번 initializer) → epilogue
비유하면 전위 순회에서 중위 순회로 바뀐 느낌이다.
'CS > Java' 카테고리의 다른 글
| [Java] 제네릭/와일드카드/타입 소거 정리 (0) | 2026.01.28 |
|---|---|
| [Java] try/catch/finally: 바이트코드 분석 (1) | 2026.01.21 |
| [Java] 중첩 클래스(Nested class) 정리 - 바이트코드 분석을 곁들인 (0) | 2026.01.17 |