티스토리 뷰
JVM의 init 메서드, 객체 초기화를 위한 인스턴스 초기화 메서드
인스턴스 초기화 메서드(instance initialization method)
JVM은 객체 인스턴스를 초기화할 때 init 이라는 고유한 메서드를 활용합니다. 예를 들어, 다음과 같이 Object 객체를 생성하는 코드가 있다고 가정해보겠습니다.
Object obj = new Object();
해당 코드를 컴파일 한 후 Byte Code를 살펴보면 다음과 같습니다.
0: new #2 // class java/lang/Object
3: dup
4: invokespecial #1 // Method java/lang/Object."<init>":()V
7: astore_1
JVM은 객체의 초기화를 위해 <init> 이라는 특별한 이름의 메서드를 호출하였습니다. 이를 인스턴스 초기화 메서드(instance initialization method)라고 하는데, 아래의 조건을 만족하는 메서드만이 인스턴스 초기화 메서드에 해당합니다.
- 클래스에 정의되어 있다.
- 이름이 init이다.
- 반환 타입이 void이다.
사실 <init> 메서드는 생성자에 해당하기도 합니다. 자바의 생성자는 자바 컴파일러에 의해 특별한 형태의 메서드인 <init> 메서드로 변환됩니다. 그리고 <init> 메서드는 JVM이 객체를 초기화할 때 호출됩니다. JVM은 이러한 방식을 통해 표준되고 일관된 객체 초기화 과정을 보장하고 있습니다.
하지만, 자바는 인스턴스 초기화 블록(Instance Initalization Block)을 지원합니다. 아래와 같은 Member 클래스가 있다고 할 때, 다음과 같이 인스턴스 초기화 블록을 활용할 수 있습니다.
public class Member {
private String name;
// 인스턴스 초기화 블록
{
this.printName(name);
}
public Member(String name) {
System.out.println("Member Constructor called");
this.name = name;
}
public void printName(String name) {
System.out.println("name = " + name);
}
}
Member 객체를 생성해보면 다음과 같이 출력된 것을 확인할 수 있습니다.
name = null
Member Constructor called
결과를 보면 생성자 이전에 인스턴스 초기화 블록이 실행된 것을 알 수 있습니다. null이 출력된 이유는 해당 코드이 컴파일 바이트 코드를 보면됩니다. 다음은 컴파일된 .class 파일을 디컴파일한 코드입니다.
public class Member {
private String name;
public Member(String name) {
this.printName(this.name);
System.out.println("Member Constructor called");
this.name = name;
}
public void printName(String name) {
System.out.println("name = " + name);
}
}
이를 보면 인스턴스 초기화 블록이 생성자로 들어가게 되고, 아직 name 필드에 값이 할당되지 않은 상태이기 때문에 null이 출력된 것을 알 수 있습니다.
즉, 생성자와 인스턴스 초기화 블록은 코드 작성 시에는 분리되어 있지만, 컴파일 된 Byte Code 수준에선 동일한 인스턴스 초기화 메서드에 해당함을 확인할 수 있는 것입니다. 그럼 다음과 같이 name 필드에 변수가 선언된 클래스라면 어떨까요?
public class Member {
private String name = "GilDong";
{
this.printName(name);
}
public Member(String name) {
System.out.println("Member Constructor called");
this.name = name;
}
public void printName(String name) {
System.out.println("name = " + name);
}
}
코드를 실행해보면 다음과 같이 출력되는 것을 확인할 수 있습니다.
name = GilDong
Member Constructor called
이전과 다르게 name에 값이 할당된 상태임을 확인할 수 있는데, 해당 클래스의 Byte Code를 보면 다음과 같습니다.
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: ldc #7 // String GilDong
7: putfield #9 // Field name:Ljava/lang/String;
10: aload_0
11: aload_0
12: getfield #9 // Field name:Ljava/lang/String;
15: invokevirtual #15 // Method printName:(Ljava/lang/String;)V
18: getstatic #19 // Field java/lang/System.out:Ljava/io/PrintStream;
21: ldc #25 // String Member Constructor called
23: invokevirtual #27 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
26: aload_0
27: aload_1
28: putfield #9 // Field name:Ljava/lang/String;
31: return
가장먼저 init 메서드를 통해 객체가 생성된 후, name 변수에 "GilDong"값이 할당되었음을 확인할 수 있습니다. 이후, 인스턴스 초기화 블록에 의해 printName()이 호출되고, 곧이어 생성자가 호출됨을 확인할 수 있습니다. 따라서, 객체 생성 이후 값이 바로 할당되기 때문에 null이 찍히지 않는 것입니다. 마찬가지로 컴파일된 .class 파일을 디컴파일한 코드입니다.
public class Member {
private String name = "MangKyu";
public Member(String name) {
this.printName(this.name);
System.out.println("Member Constructor called");
this.name = name;
}
public void printName(String name) {
System.out.println("name = " + name);
}
}
참고로 Class의 초기화를 위한 메서드도 존재하는데, 이는 static 블록을 지정해주면 되고, 컴파일 시에 cinit 메서드로 컴파일 됩니다.
public class Member {
private String name = "GilDong";
static {
System.out.println("Class Init called");
}
public Member(String name) {
System.out.println("Member Constructor called");
this.name = name;
}
public void printName(String name) {
System.out.println("name = " + name);
}
}
참고
'Language > JAVA' 카테고리의 다른 글
[JAVA]클래스 로더(Class Loader)란? (0) | 2024.10.22 |
---|---|
[JAVA]JDK 종류 파헤치기(Open JDK, Oracle JDK, Adpot, Corretto, Zulu) (1) | 2024.10.18 |
[JAVA]익명 클래스(Anonymous Class) (0) | 2024.09.24 |
[JAVA]지역 클래스(Local Class) (0) | 2024.09.24 |
[JAVA]내부 클래스(Inner Class) (0) | 2024.09.23 |
- Total
- Today
- Yesterday