내부 클래스는 왜 static 으로 선언할까?
이너클래스 선언시 위와같이 static 으로 선언해 달라는 IDEA의 잔소리를 한번쯤 봤을것입니다.
Nested class (내부 클래스) 에 대해 간략히 알아보는 시간을 가지며 왜 IDEA 는 내부 클래스 선언시
위와같은 잔소리를 하는것인지 알아보겠습니다.
내부 클래스의 장점
내부 클래스를 사용하는 이유는 ORACLE-java Tutorial 에 아주 간결하게 명시 되어있습니다.
From the Java Tutorial:
번역기 돌리실 여러분들을 위해 제가 좀 거들어 드리자면
- A와 B클래스 2개의 클래스가 존재할때 B클래스를 의존하는 다른 클래스가 존재 하지 않고 오로지 A 클래스 에서만 의존하고 있다면 서로 같은 클래스에 선언하여 패키지를 간소화 하는 장점이 있다.의존한다 라는말이 익숙하지 않으신 분들을 위해 -> A 가 B 를 의존한다 = A 클래스가 B 의 자원,기능 을 필요로 한다.
// A 가 B 를 의존하고있음. 이말은 즉슨 A 라는 클래스는 B라는 클래스 없이 작동하지 않음.
public class A{
private B b;
private String useB;
public A(B b){
this.b=b;
}
public void useB(){
this.useB = b.whatever();
}
}
- A와 B클래스 2개의 클래스가 존재할때 B 클래스 가 A 클래스의 private 멤버변수에 접근 할 경우
B 클래스를 private class 로 선언하여 B를 캡슐화 할 수 있으며 A 클래스와 유동적인 자원 교환이 가능해집니다. - 가독성과 유지보수성이 향상된다.... 클린코드의 치트키같은 말이니까 넘어가도 좋을것같습니다.
내부 클래스는 어떤종류가 있을까?
1.인스턴스 클래스
public class Outer{
// 인스턴스 클래스
public class Inner{
}
}
2.스테틱 클래스
public class Outer{
// 스테틱 클래스
static public class Inner{
}
}
- 로컬 클래스
public class A{
// 메소드 내부에 생성된 클래스
public void localClass(){
class B{
public String abc;
}
B b = new B();
b.abc = "localClass";
}
}
- 익명 클래스
public interface A{
void anonymous();
}
public class Main{
public satic void main(String[]args){
// 익명클래스
A a = new A(){
@override
void anonymous(){
System.out.println("익명클래스");
};
}
}
}
위의 4가지 Innerclass 종류 중 이번 포스팅의 뜨거운 감자인 인스턴스 클래스와 스테틱 클래스는 어떤것이 다른지 자세히 살펴보겠습니다.
- 인스턴스 클래스는 외부 클래스의 인스턴스화 없이 내부 클래스에 접근이 불가능하다.
public class Outer{
public class Inner{
}
}
public
2. 인스턴스 내부 클래스는 외부 클래스를 참조하는 변수를 선언하지 않아도 바이트 코드 변환시 자동으로 외부를 참조하는 변수를 만든다.
오늘 이 포스팅을 작성한 핵심 내용이기 때문에 색깔을 넣어보았습니다.스텝바이 스텝으로 따라가 보겠습니다.
public class OuterClass {
int o = 10;
class Inner {
int i = 20;
}
}
현재 Inner 라는 클래스는 인스턴스 클래스로 선언이 되어있습니다. 이를 클래스 파일로 컴파일 후 바이트 코드를 확인 해보면
위 사진처럼 InnerClass 가 OuterClass 를 참조하고 있음을 알 수 있습니다. (this$0 -> OuterClass 를 참조하고있는 바이트 코드에서만 보여 Hidden 변수)
위 차이점 덕에 IDE 애플리케이션에서 내부에서 외부를 참조하지 않을경우 static 클래스로 선언 하라는 잔소를
했음을 알 수 있습니다. 이또한 차근차근 짚어나가 보겠습니다.
static 으로 선언 해야 하는 이유.
- GC 의 수거대상을 벗어날 수 있습니다.
- 아래 코드를 살펴보겠습니다.
@Test
void leak(){
ArrayList<EnclosedClass> al = new ArrayList<>();
int counter = 0;
while (20>counter)
{
al.add(new EnclosingClass(100000000).getEnclosedClassObject());
System.out.println(counter++);
}
}
public class EnclosingClass {
private int[] data;
public EnclosingClass(int size)
{data = new int[size];}
class EnclosedClass{}
EnclosedClass getEnclosedClassObject()
{
return new EnclosedClass();
}
}
<<<실행 결과>>>
펑~🎇
위같은 결과가 나온 이유는 GC 가 정상적으로 Unreachable 한 데이터를 수거해가지 못했기때문에 생긴 문제 입니다.
위 코드에서 ArrayList 는 InnerClass 에 대한 데이터만 저장하기 때문에 외부 OuterClass 즉
new int[100000000] 의 데이터를 갖고 있는 클래스는 힙영역에서 GC 의 대상이 되어 수거가 되었어야 합니다.
하지만 내부클래스에서 외부클래스를 참조하고 있는 관계가 형성되어 GC 는 엄청난 데이터를 머금고 있는 외부 클래스를 GC의 대상으로 보지 않고 힙영역에 계속 쌓게 됩니다.
결론-> Heap 영역 퍼버벙~
정상적으로 외부 클래스가 더이상 참조 되지 않았을때 GC 의 소거 대상이 되기 위해선 둘의 참조관계를 끊어야합니다.
- 방법은 위에 명시되어있듯이 static 만 선언해주면 됩니다.
public class OuterClass {
int o = 10;
static class Inner {
int i = 20;
}
}
위 코드를 컴파일 했을경우 아래 .class 바이트 코드는 아래와 같습니다.
static 키워드만 붙였을 뿐인데 this$0 의 히든변수가 사라졌고 더이상 외부 클래스를 참조하지 않습니다.
위에 메모리 Leak 문제를 야기 시켰던 코드 내부 클래스에 static 클래스로 선언한후 다시 돌려보면
@Test
void leak(){
ArrayList<EnclosedClass> al = new ArrayList<>();
int counter = 0;
while (20>counter)
{
al.add(new EnclosingClass(100000000).getEnclosedClassObject());
System.out.println(counter++);
}
}
public class EnclosingClass {
private int[] data;
public EnclosingClass(int size)
{data = new int[size];}
static class EnclosedClass{}
EnclosedClass getEnclosedClassObject()
{
return new EnclosedClass();
}
}
더이상 메모리 Leak 문제를 야기시키지 않습니다. 외부에 어마어마한 데이터를 머금고 있는 인스턴스가 GC 의 대상이 되었기 때문입니다.
결론.
내부클래스가 외부클래스를 참조하지 않는다면 인스턴스 클래스보다는 static 클래스로 선언하자.
reference:
https://www.infoworld.com/article/3526554/avoid-memory-leaks-in-inner-classes.html
https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html
https://tecoble.techcourse.co.kr/post/2020-11-05-nested-class/
Author And Source
이 문제에 관하여(내부 클래스는 왜 static 으로 선언할까?), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@dkajffkem/내부-클래스는-왜-static-으로-선언할까저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)