Inner Class에 Static을 붙여줘야 하는 이유는?
Inner Class를 생성할 때 흔히 Static을 붙여주는 이유는 무엇일까요? 예를 들어 다음과 같은 예시가 있다고 가정해봅시다.
public class OuterClass {
int field = 10;
class InnerClass {
int inner_field = 20;
}
}
해당 클래스를 컴파일 후에 만들어지는 .class 파일을 살펴보면 다음과 같습니다.
해당 파일을 인텔리제이로 다시 열어서 디컴파일을 해보면
class OuterClass$InnerClass {
int inner_field;
OuterClass$InnerClass(OuterClass this$0) {
this.this$0 = this$0;
this.inner_field = 20;
}
}
다음과 같은 InnerClass가 만들어진 것을 볼 수 있습니다. OuterClass를 생성자의 인수로 받아 사용하고 있는 것을
파악할 수 있습니다.
이것을 다시 말하면, 비정적(non-static) 클래스의 인스턴스는 외부 클래스의 인스턴스와 연결되어 있음을 뜻합니다.
따라서 다음과 같은 호출이 가능합니다.
public class OuterClass {
int field = 10;
class InnerClass {
int inner_field = 20;
int newField = OuterClass.this.field;
}
}
Outer 클래스의 인스턴스를 나타내는 OuterClass.this로 Outer클래스에서 생성된 인스턴스의 필드인 10을 Inner클래스
인스턴스에서 쓸 수가 있는것입니다.
public class Main {
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
OuterClass.InnerClass innerInstance = outerClass.new InnerClass();
System.out.println(innerInstance.newField);
}
}
따라서 다음과 같이, OuterClass에 선언된 내부 클래스의 인스턴스를 생성하면 외부 클래스에서 선언된 인스턴스 변수에 접근을 할 수 가 있습니다.
Static 내부 클래스는 this를 통해 OuterClass의 인스턴스 변수에 접근을 하지 못하는데, 그럼 Static을 안 쓰는게
더 좋은거 아닌가? 라는 생각을 하실 수도 있습니다.
하지만 비정적 내부 클래스의 인스턴스는 메모리 누수에 취약한 위험성을 지니고 있습니다.
Inner Class의 메모리 누수 현상
다음과 같은 예가 있다고 해봅시다.
public class OuterClass {
int[] test;
public OuterClass() {
this.test = new int[100_000_000];
}
public InnerClass getInnerClass() {
return new InnerClass();
}
class InnerClass {
public InnerClass() {
System.out.println("Inner클래스 생성");
}
}
}
OuterClass는 test라는 int 배열을 인스턴스 변수로 지니고 있습니다.
OuterClass가 생성될 때 생성자 호출로 인해 1억개의 공간을 지닌 int 배열이 생성됩니다.
또한 getInnerClass라는 인스턴스 메서드를 지니고 있는데, 해당 메서드는 내부 클래스의 인스턴스를 만들어
반환해주는 역할을 수행합니다.
public class Main {
public static void main(String[] args) {
List<OuterClass.InnerClass> list = new ArrayList<>();
for (int i=0; i<10; i++) {
list.add(new OuterClass().getInnerClass());
}
}
}
메인 메서드에서는 OuterClass 인스턴스의 getInnerClass로 InnerClass 인스턴스를 10번 생성하여 list에 담고자 합니다.
만약 InnerClass가 OuterClass를 참조하고 있지 않다면, new OuterClass()는 참조하고 있는 참조변수가 없기 때문에
GC(Garbage Collection)으로 인해 메모리가 정리될 것입니다.
OuterClass의 인스턴스가 정리되지 않는다면 1억개의 int 배열을 10억개 생성하게 되니 4000MB - 4GB 가량의 메모리를
잡아먹게 될 것이고, 이로 인해 OutOfMemoryError가 일어나게 될 것입니다.
실행 결과 9번의 InnerClass를 생성 후 리스트에 담은 다음 10번째 실행할 때 메모리 힙영역이 가득 차게 되어 OutofMemoryError가 난 것을 확인할 수 있습니다.
이런 메모리 누수 위험을 피하기 위해 내부 클래스에는 꼭 static을 붙여야 하는 것입니다.
Static 내부 클래스
아까와 똑같은 경우이지만 클래스 앞에 static만 붙여주게 된다면 어떻게 될까요?
public class OuterClass {
int[] test;
public OuterClass() {
this.test = new int[100_000_000];
}
public InnerClass getInnerClass() {
return new InnerClass();
}
static class InnerClass {
public InnerClass() {
System.out.println("Inner클래스 생성");
}
}
}
메인 메서드에서는 Inner 클래스의 인스턴스를 이전의 10번보다 훨씬 큰 500번 담아주도록 하겠습니다.
public class Main {
public static void main(String[] args) {
List<OuterClass.InnerClass> list = new ArrayList<>();
for (int i=0; i<500; i++) {
list.add(new OuterClass().getInnerClass());
}
}
}
그 결과는 다음과 같습니다.
500번의 내부 클래스 인스턴스를 생성하고 정상적으로 프로그램이 종료된 것을 확인할 수 있습니다.
만약 내부 클래스의 인스턴스가 외부 클래스의 인스턴스를 참조하고 있어서 GC처리가 되어 있지 않았다면
이전과 같이 OutOfMemoryError가 발생했을 것입니다.
실제로 static 내부 클래스를 컴파일 후 디컴파일 해보면
class OuterClass$InnerClass {
public OuterClass$InnerClass() {
System.out.println("Inner클래스 생성");
}
}
다음과 같이 이전에 static이 붙어있지 않은 내부클래스와 다르게 외부 클래스를 생성 인자로 받고 있지 않는 것을
알 수 있습니다.
Static을 붙여줌으로 인해서 외부 클래스와의 참조가 끊어지게 되고 메모리 누수 위험을 예방 할 수 있는 것입니다.
이상으로 내부 클래스에 static을 붙여줘야 하는 이유에 대해서 살펴봤습니다.
긴 글 읽어주셔서 감사합니다.
참고자료
☕ 내부 클래스는 static 으로 선언 안하면 큰일 난다
Inner 클래스의 문제점 인텔리제이와 같은 IDE에서 내부 클래스를 선언하여 사용하면 다음과 같이 경고 메세지가 뜰 것이다. (내부 클래스가 외부의 멤버를 참조하여 사용하지 않을 경우) 메세지
inpa.tistory.com
'Java' 카테고리의 다른 글
자바 예외 관련 개념 정리 (0) | 2023.05.07 |
---|---|
제너릭이란? (1) | 2023.04.12 |
추상클래스와 인터페이스 (2) | 2023.04.09 |
자바 변수를 메모리 관점에서 뜯어보기 (0) | 2023.04.01 |
가비지 컬렉션에 대해 (0) | 2023.03.21 |