중첩 클래스(Nested Class)는 무엇이고 4가지 종류 중 정적 내부 클래스
와 비정적 내부 클래스
에 대해 다뤄보고자 한다.
중첩 클래스란(Nested Class)
먼저 중첩 클래스에 대해 설명을 해야하는데, 중첩 클래스란 말 그대로 다른 클래스의 내부에 존재하는 클래스를 의미한다. 중첩클래스를 포함하는 외부 클래스를 Outer class라고 하며 내부에 포함된 클래스를 nested class 또는 Inner class라고 한다.
중첩 클래스는 4가지 종류가 존재한다.
- 정적 멤버 클래스(static inner class) : static 키워드를 이용해서 클래스가 정의된 경우
- 비정적 멤버 클래스(non-static inner class) : Outer 클래스의 멤버변수나 메소드처럼 클래스가 정의된 경우
- 익명 클래스(anonymous class) : 익명 클래스를 이용해서 클래스가 정의된 경우
- 지역 클래스(local class) : Outer 클래스의 특정 메소드 또는 초기화 블록에서 클래스가 정의된 경우
class Outer {
class InstanceInner {} // non static inner class
static class StaticInner {} // static inner class
void myMethod() { // local class
class LocalInner {}
}
// anonymous class
Ex ex = new Ex() {
public void getEx() {
System.out.println("anonymous class");
}
};
}
}
interface Ex {
public void getEx();
}
중첩 클래스는 특정 클래스를 자신의 클래스 내부적인 용도로만 사용하고자 할때 효율적이기 때문에 불필요한 노출을 줄이면서 캡슐화를 통해 유지 보수하기 좋은 코드를 작성하게 된다.
이펙티브 자바에서는 중첩 클래스는 자신을 감싼 바깥 클래스에서만 쓰여야 하며, 그 외의 쓰임새가 있다면 톱 레벨 클래스로 만들어야 한다고 권장하고 있으며 특히 static
으로 선언하도록 권장한다. (아이템 24)
그렇다면 왜인지 알아보자!!
정적 멤버 클래스
- static이 붙는 중첩 클래스
- 동일한 static 멤버들을 사용 가능
- static의 특징에 따라 외부 인스턴스 멤버의 직접참조가 불가능
static
예약어가 있음으로 인해 독립적으로 생성할 수 있다.
정적 멤버 클래스는 바깥 클래스의 private 멤버에도 접근할 수 있다는 점을 제외하고 일반 클래스와 쓰임새는 동일하다.
class Outer {
static int x = 10;
int y = 20;
private static int z = 30;
static class StaticInner { // static inner class
void get() {
System.out.println("x: " + x);
System.out.println("z: " + z);
}
}
}
Outer.StaticInner staticIneer = new Outer.StaticInner();
staticIneer.get();
비정적 멤버 클래스
- Inner class라고 하며 외부 인스턴스에 대한 참조가 유지된다.
- 외부 인스턴스는 내부 클래스를 new를 통한 인스턴스 할당으로 멤버변수처럼 사용할 수 있다.
- 외부에 대한 참조가 유지되므로 내부 클래스도 외부 클래스의 자원을 사용할 수 있다.
class Outer {
static int x = 10;
int y = 20;
public int z = 30;
class InstanceInner {
void get() {
System.out.println("x: " + x);
System.out.println("y: " + y);
System.out.println("z: " + z);
}
}
}
Outer outer = new Outer();
Outer.InstanceInner insttanceInner = outer.new InstanceInner();
insttanceInner.get();
비정적 내부 클래스를 생성하는 경우에는 반드시 Outer
객체를 생성한 뒤 객체를 이용해서 생성해야 한다. 즉, 비정적 내부 클래스는 Outer 클래스
에 대한 참조가 필요하다는 것이다.
그렇다면 왜 멤버 클래스는 static으로 선언하기를 권장할까?
바로 Outer 객체에 대한 참조 때문이다.
위의 예제처럼 InstanceInner
와 같은 중첩 클래스를 선언하면 인스펙터가 다음과 같이 경고를 해준다.
경고 주제는 메모리 누수
가능성이 있기 때문이다.
인용: 이펙티브 자바
정적 멤버 클래스와 비정적 멤버 클래스의 구문상 차이는 단지 static이 붙어있고 없고 뿐이지만, 의미상 차이는 의외로 꽤 크다. 비정적 멤버 클래스의 인스턴스는 바깥 클래스의 인스턴스와 암묵적으로 연결되기 때문에 바깥 클래스는 더 이상 사용되지 않지만 내부 클래스의 참조로 인해 GC가 수거하지 못해서 바깥 클래스의 메모리 해제를 하지 못하는 경우가 발생할 수 있기 때문이다. 이 문제는 IDE에서 경고해주기 때문에 흔하게 볼 수 있으며, 조슈아 블로흐가 이펙티브 자바에서도 강조하고 있는 내용이기도 하다.
비정적 멤버 클래스의 인스턴스와 바깥 인스턴스 사이의 관계는 멤버 클래스가 인스턴스화될 때 확립되며, 더 이상 변경할 수 없다. 이 관계는 바깥 클래스의 인스턴스 메서드에서 비정적 멤버 클래스의 생성자를 호출할 때 자동으로 만들어지는 게 보통이지만, 드물게는 직접
바깥 인스턴스의 클래스.new MemberClass(args)
를 호출해 수동으로 만들기도 한다. 예상할 수 있듯, 이 관계 정보는 비정적 멤버 클래스의 인스턴스 안에 만들어져 메모리 공간을 차지하며, 생성 시간도 더 걸린다.
멤버 클래스에서 바깥 인스턴스에 접근할 일이 없다면 무조건 static을 붙여서 정적 멤버 클래스로 만들자. static을 생략하면 바깥 인스턴스로의 숨은 외부 참조를 갖게 된다. 앞서도 얘기했듯 이 참조를 저장하려면 시간과 공간이 소비된다. 더 심각한 문제는 가비지 컬렉션이 바깥 클래스의 인스턴스를 수거하지 못하는 메모리 누수가 생길 수 있다는 점이다(아이템 7). 참조가 눈에 보이지 않으니 문제의 원인을 찾기 어려워 때때로 심각한 상황을 초래하기도 한다.
결론
static
이 아닌 멤버 클래스의 인스턴스는 바깥 클래스의 인스턴스와 암묵적으로 연결된다.- 왜냐하면
static
이 아닌 멤버 클래스는 바깥 인스턴스 없이는 생성할 수 없기 때문이다. - 두 클래스의 관계는 멤버 클래스의 인스턴스 안에 만들어지며, 메모리를 차지한다. 생성도 느리다.
- 바깥 클래스 인스턴스의 참조를 멤버 클래스가 갖고 있으므로, 바깥 클래스 인스턴스가 쓰레기 수거 대상에서 빠지게 된다.
- 이는 메모리 누수를 불러일으킬 수 있는 치명적인 위험요소
결국 외부 인스턴스에 대한 참조가 필요하지 않고 내부 클래스가 독립적으로 사용된다면 static nested class로 만드는 것이 낫다.
참고 출처
- https://yhmane.tistory.com/225
- https://www.geeksforgeeks.org/difference-between-static-and-non-static-nested-class-in-java/
- https://jobjava00.github.io/language/java/basic/nested-class/
- https://tecoble.techcourse.co.kr/post/2020-11-05-nested-class/
- https://johngrib.github.io/wiki/java-inner-class-may-be-static/
- https://velog.io/@agugu95/왜-Inner-class에-Static을-붙이는거지
'java' 카테고리의 다른 글
[모던 자바 인 액션] 동작 파라미터화 (0) | 2024.07.07 |
---|---|
[모던 자바 인 액션] 자바 8, 9, 10, 11에서 일어난 일 (0) | 2024.07.07 |
자바 제네릭스 (0) | 2024.07.07 |
java 버전별 차이 & 특징 (0) | 2024.07.07 |
Object 클래스 (0) | 2024.07.07 |