그 동안 프로젝트를 진행할 때 내부 클래스를 이용해 데이터를 전달해왔는데, IDE에서 Warning을 띄워주기도 했고 메모리 누수의 위험이 있다는 글을 보고 이 부분에 대해서는 간단하게라도 이해하고 넘어가야 한다고 생각했다.
이번 글에서는 간단하게 위험한 부분에 대해서만 정리해보고 추후에 더 자세히 정리해보도록하자.
중첩 클래스는 쉽게 말하자면 클래스 내부에 정의된 클래스를 의미한다. 중첩 클래스는 특정 클래스가 한 곳에서만 사용될 때 논리적으로 군집화하기 위해 사용한다. 불필요한 노출을 줄이면서 캡슐화를 할 수 있고 가독성과 유지 보수하기 좋은 코드를 작성하는데 장점을 가지고 있다.
정적 클래스인지 비정적 클래스인지를 나누는 기준은 static
예약어가 붙어있는지 안붙어있는지로 판단할 수 있다.
this
예약어로 외부 클래스의 인스턴스를 호출할 수 있다.(가장 중요한 점은 비정적 클래스는 외부 인스턴스에 대한 참조가 유지되는 것이다! 👊)
public class Outer {
private int a;
class Inner {
private int b;
}
}
// 1
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
// 2
Outer.Inner inner = new Outer().new Inner();
static
멤버는 가질 수 없다.public class Outer {
void outerPrint() {
class Local {
void localPrint() {
System.out.println("Local");
}
}
}
}
instanceof
나 클래스의 이름이 필요한 작업은 수행할 수 없다.interface AnonymousInterface {
void print();
}
public class Outer {
int a = 10;
void anonymous {
new AnonymousInterface() {
@Override
public void print() {
System.out.println(a);
}
}
}
}
private
멤버에도 접근할 수 있다.public class Outer {
private int a;
static class Inner {
private int b;
}
}
Outer.Inner inner = new Inner();
// Outer.class
public class Outer {
private int out;
public static class StaticInner {
private int in;
}
public class NonStaticInner {
private int in;
}
}
먼저 정적 내부 클래스부터 컴파일해서 class 파일로 변환한 뒤 javap -p
명령어로 Disassembler 결과를 확인해보자. 결과로는 멤버 변수와 기본 생성자에 대한 참조를 가지고 있는 것을 확인할 수 있다.
$ javap -p out.production.study-java-test.Outer\$StaticInner
Compiled from "Outer.java"
public class Outer$StaticInner {
private int in;
public Outer$StaticInner();
}
반대로 비정적 내부 클래스를 컴파일해서 확인해보면, 멤버변수와 기본 생성자를 비롯한 외부 클래스까지 참조하고 있는 것을 확인할 수 있다.
$ javap -p out.production.study-java-test.Outer\$NonStaticInner
Compiled from "Outer.java"
public class Outer$NonStaticInner {
private int in;
final Outer this$0; // 여기!!
public Outer$NonStaticInner(Outer);
}
자바에서 객체가 삭제되는 시점은 객체가 더 이상 사용되지 않을 때다. 하지만 위에서 확인했듯이, 내부 클래스는 외부 클래스를 항상 참조하고 있다. 따라서 외부 클래스가 삭제되더라도 내부 클래스가 살아있게 되어 메모리 누수가 발생하게 된다.
결국, 정적 내부 클래스로 선언하게 된다면 메모리 누수의 원인을 예방하고 클래스의 각 인스턴스당 더 적은 메모리를 사용하기 때문에 외부 인스턴스에 대한 참조가 필요하지 않다면, 정적 중첩 클래스로 만드는 것이 좋다. 반대로 비정적 클래스는 어댑터 패턴을 이용하여 외부 클래스를 다른 클래스로 제공할 때 사용하면 좋다.