1. Java에서의 객체 초기화 순서: 무엇이 먼저, 무엇이 나중인가?
Java에서 객체를 만드는 일은 단순히 “메모리를 잡고 값을 채운다”가 아닙니다. 엄격한 절차를 따르는 일종의 의식에 가깝죠. 로켓 발사에 비유하자면, 각 단계를 수행하는 순서가 정해져 있습니다.
다음과 같이 작성하면:
Person vasya = new Person("Vasya", 30);
Java는 다음 단계를 수행합니다:
- 객체에 메모리를 할당하고 모든 필드를 기본값으로 설정합니다.
- 필드 선언 시 지정한 명시적 초기화를 수행합니다(선언과 동시에 값을 준 경우).
- 모든 비정적(일반) 초기화 블록을 클래스에 작성된 순서대로 실행합니다.
- new로 호출한 생성자의 본문을 실행합니다.
각 단계를 자세히 살펴봅시다.
2. 필드 초기화
기본값
객체가 막 생성될 때(아직 아무 값도 명시적으로 대입하기 전) 모든 필드는 다음과 같은 기본값을 받습니다:
| 필드 타입 | 기본값 |
|---|---|
|
|
|
|
|
|
|
'\u0000' (널 문자) |
| 참조 타입(String, 기타 객체) | |
명시적 초기화
필드 선언 시 바로 값을 지정했다면, 그 값은 기본값이 설정된 후에, 하지만 생성자가 실행되기 전에 대입됩니다.
public class Person {
private String name = "이름 없음";
private int age = 18;
}
기본 생성자로 객체를 만들면 이 값이 그대로 유지됩니다. 매개변수가 있는 생성자를 사용하면 대개 이 값들이 덮어써집니다.
3. 초기화 블록: 왜 필요하고 어떻게 동작하나
Java에서는 이른바 비정적(일반) 초기화 블록을 선언할 수 있습니다. 이 블록은 객체를 만들 때마다 필드 초기화 직후, 하지만 생성자보다 먼저 실행됩니다.
문법:
public class Person {
{
System.out.println("비정적 초기화 블록이 실행됩니다!");
}
}
이런 블록이 여러 개 있다면, 작성된 순서대로 실행됩니다.
여러 블록이 있는 예
public class Person {
private String name = "이름 없음";
{
System.out.println("블록 1: name = " + name);
name = "수수께끼";
}
{
System.out.println("블록 2: name = " + name);
}
public Person() {
System.out.println("생성자: name = " + name);
}
}
객체를 생성하면:
Person p = new Person();
출력은 다음과 같습니다:
블록 1: name = 이름 없음
블록 2: name = 수수께끼
생성자: name = 수수께끼
초기화 블록은 언제 유용한가?
실무에서는 초기화 블록을 자주 사용하지는 않습니다. 대체로 블록에서 할 수 있는 일은 필드 선언부나 생성자에서 할 수 있기 때문입니다. 다만 생성자가 여러 개이고, 그들 모두에서 공통으로 수행되어야 하는 초기화 로직이 있을 때는 블록이 편리할 수 있습니다.
4. 생성자: 초기화의 마지막 단계
모든 필드가 값(기본값 또는 명시적 값)을 가진 뒤, 모든 초기화 블록이 실행되면 생성자가 호출됩니다 — 바로 new 다음에 지정한 그 생성자입니다.
생성자에서는 보통 최종 필드 값을 설정하고, 매개변수를 받아 입력값에 따라 달라지는 초기화를 수행합니다.
public class Person {
private String name;
private int age;
public Person(String name, int age) {
System.out.println("생성자: name = " + name + ", age = " + age);
this.name = name;
this.age = age;
}
}
5. 전체 초기화 순서 데모
초기화가 어떤 순서로 진행되는지 명확히 보여주는 클래스를 작성해 봅시다.
public class Person {
private String name = "이름 없음";
private int age = 18;
{
System.out.println("초기화 블록: name = " + name + ", age = " + age);
age = 21;
}
public Person() {
System.out.println("매개변수 없는 생성자: name = " + name + ", age = " + age);
}
public Person(String name, int age) {
System.out.println("매개변수 있는 생성자: name = " + name + ", age = " + age);
this.name = name;
this.age = age;
}
public void printInfo() {
System.out.println("Person: name = " + name + ", age = " + age);
}
}
이제 메인 클래스(예: Main)에서:
public class Main {
public static void main(String[] args) {
System.out.println("객체 p1을 생성합니다:");
Person p1 = new Person();
p1.printInfo();
System.out.println("\n객체 p2를 생성합니다:");
Person p2 = new Person("Petya", 30);
p2.printInfo();
}
}
출력은 대략 다음과 같습니다:
객체 p1을 생성합니다:
초기화 블록: name = 이름 없음, age = 18
매개변수 없는 생성자: name = 이름 없음, age = 21
Person: name = 이름 없음, age = 21
객체 p2를 생성합니다:
초기화 블록: name = 이름 없음, age = 18
매개변수 있는 생성자: name = 이름 없음, age = 21
Person: name = Petya, age = 30
주의: 매개변수 있는 생성자로 들어오는 시점에는 필드 값이 아직 이전 값(초기화 블록 이후의 값)이고, 생성자 실행이 끝난 뒤에야 필드가 최종 값으로 설정됩니다.
6. 초기화 순서 다이어그램
다음은 작은 블록 다이어그램입니다:
flowchart TD
A[메모리 할당, 기본값 설정] --> B[필드의 명시적 초기화]
B --> C[비정적 초기화 블록 실행]
C --> D[생성자 실행]
D --> E[객체 사용 준비 완료]
7. 특징과 주의할 점
정적 필드와 정적 블록
이 강의에서는 비정적(일반) 필드와 블록만 다룹니다. 정적 필드와 정적 블록은 클래스가 로드될 때 단 한 번 초기화되며, 객체를 만들 때마다 초기화되지는 않습니다. 이에 대해서는 캡슐화 주제에서 더 자세히 다룹니다.
무엇이 언제 초기화될까?
- 필드 — 객체를 생성할 때마다.
- 초기화 블록 — 객체를 생성할 때마다.
- 정적 필드와 블록 — 클래스 로드 시 단 한 번.
초기화 블록에서 필드에 접근해도 될까?
가능합니다! 다만 필드가 블록 아래쪽에 선언되어 있어도 이미 존재는 하며, 명시적 초기화 이전에 접근하면 기본값을 얻게 된다는 점을 기억해야 합니다.
8. 객체 초기화에서 흔한 실수
오류 1: 필드가 선언되기 전에 초기화될 것이라고 기대한다.
Java에서는 클래스 내에서의 필드와 초기화 블록의 선언 순서가 중요합니다. 초기화 블록에서 아래쪽에 선언된 필드를 참조하면 그 필드는 이미 존재하지만 아직 명시적으로 초기화되기 전이므로 기본값을 갖습니다.
오류 2: 초기화 블록과 생성자에서 초기화를 중복한다.
초심자들은 같은 로직을 블록과 생성자에 중복해 넣곤 합니다. 블록을 쓰거나, 생성자를 쓰거나, 아니면 this(...)를 통해 한 생성자에서 다른 생성자를 호출하는 방식으로 공통화하는 편이 좋습니다.
오류 3: 정적 필드가 객체 생성 때마다 초기화된다고 오해한다.
정적 필드와 블록은 다르게 동작합니다 — 클래스가 로드될 때 한 번만 초기화됩니다.
오류 4: 초기화되지 않은 참조형 필드를 사용한다.
참조형 필드를 명시적으로 초기화하지 않았다면(예: String name;), 초기화 블록이나 생성자에서 값을 대입하기 전까지는 그 값이 null입니다.
GO TO FULL VERSION