24. 멤버 클래스는 되도록 static으로 만들라
📝 전체 내용 간단 요약
아이템 24에서는 클래스 안에 클래스를 정의할 때(static 중첩 클래스와 non-static 내부 클래스) 외부 클래스의 데이터를 사용할 필요가 없다면 static 중첩 클래스를 사용해야 한다고 권장. 이유는 static 클래스가 외부 클래스의 인스턴스에 대한 참조를 갖지 않아 메모리를 더 효율적으로 관리할 수 있고, 클래스 간 불필요한 의존성을 최소화하며, 코드의 가독성과 유지보수성을 높이기 때문입니다.
📚 미리 알아야 할 내용 요약
📍 멤버 클래스 (중첩 클래스)
하나의 클래스 안에 또 다른 클래스를 정의할 수 있음
종류는 크게 두 가지:
static 중첩 클래스
외부 클래스의 특정 인스턴스와 독립적으로 존재할 수 있음
외부 클래스 인스턴스 참조 없음, 독립적
non-static 내부 클래스
외부 클래스의 특정 인스턴스에 종속적으로 존재함
외부 클래스의 모든 멤버에 접근 가능
📍 static 키워드
static
은 클래스 자체에 속한다는 의미클래스의 특정 객체가 아닌, 클래스 이름으로 직접 접근 가능
모든 객체가 공유하는 값이나 기능을 정의할 때 사용함
예: 자동차의 바퀴 수(
static
)는 모든 자동차가 공유하지만, 자동차의 색상은 각 자동차마다 다르므로 공유하지 않습니다.
🚩 주의사항
⚠️ non-static 내부 클래스는 메모리 누수를 일으킬 수 있다!
non-static 내부 클래스는 자동으로 외부 클래스의 인스턴스(객체)를 참조함
내부 클래스가 존재하는 동안 외부 클래스도 계속 메모리에 남아있게 됨
필요 이상으로 오랫동안 유지되면 메모리 누수가 발생할 수 있음
메모리 누수란? 더 이상 사용하지 않는 데이터가 계속 메모리를 차지하여, 프로그램 성능이 떨어지거나 심하면 충돌을 일으키는 현상입니다.
⭐ 중요한 점
🔑 내부 클래스가 외부 클래스의 데이터를 쓰지 않으면 static으로 선언하자!
static 중첩 클래스는 외부 클래스의 인스턴스와 연결되지 않음
따라서 외부 클래스의 인스턴스가 메모리에 남지 않아 메모리 효율성이 높음
클래스 간 불필요한 의존성을 최소화하고, 가독성과 유지보수성을 증가시킴
❌ 잘못된 예제 (불필요한 non-static 클래스)
class Configuration {
private static final String DEFAULT_SETTINGS_FILE = "default.cfg";
// 잘못된 예: static 멤버만 접근하는데도 non-static 내부 클래스로 선언
class SettingsLoader {
public String loadSettings() {
return "Loading settings from: " + DEFAULT_SETTINGS_FILE;
}
}
public SettingsLoader getSettingsLoader() {
return new SettingsLoader();
}
}
public class Main {
public static void main(String[] args) {
Configuration config = new Configuration();
Configuration.SettingsLoader loader = config.getSettingsLoader();
System.out.println(loader.loadSettings());
}
}
🚨 문제점
SettingsLoader가 외부 클래스의 static 변수만 필요하지만 non-static으로 선언됨
이로 인해 Configuration 객체에 대한 불필요한 참조가 생겨 메모리 낭비 발생 가능
✅ 좋은 예제 (적절한 static 중첩 클래스 사용)
class StringFormatter {
// 올바른 예: 외부 클래스의 데이터가 필요 없으므로 static으로 선언
public static class Utils {
public static String reverseString(String input) {
return new StringBuilder(input).reverse().toString();
}
public static boolean isPalindrome(String input) {
return input.equals(reverseString(input));
}
}
public static void main(String[] args) {
String text = "madam";
System.out.println("Reversed: " + Utils.reverseString(text));
System.out.println("Is Palindrome: " + Utils.isPalindrome(text));
}
}
🚀 장점
외부 클래스의 객체가 필요 없어 메모리를 효율적으로 관리함
외부 클래스와의 관계가 명확해지고 코드의 유지보수가 용이해짐
✅ non-static 내부 클래스가 꼭 필요한 좋은 예제
import java.util.Iterator;
import java.util.ArrayList;
import java.util.List;
class StringList {
private List<String> strings = new ArrayList<>();
public void add(String s) {
strings.add(s);
}
public Iterator<String> iterator() {
return new StringIterator();
}
// 적절한 non-static 내부 클래스 사용 (외부 인스턴스의 데이터에 접근해야 함)
private class StringIterator implements Iterator<String> {
private int currentIndex = 0;
@Override
public boolean hasNext() {
return currentIndex < strings.size();
}
@Override
public String next() {
return strings.get(currentIndex++);
}
}
}
public class Main {
public static void main(String[] args) {
StringList list = new StringList();
list.add("Hello");
list.add("World");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
🚀 장점
StringIterator는 외부 클래스(StringList)의 데이터를 직접 사용하기 때문에 non-static으로 선언하는 것이 옳습니다.
외부 클래스의 상태를 활용하여 정확한 동작을 구현합니다.
🤔 어려운 점
암시적 참조 개념 이해하기
non-static 내부 클래스가 외부 클래스의 객체를 자동으로 참조한다는 개념이 처음에는 이해하기 어려울 수 있습니다.
이는 내부 클래스가 외부 클래스의 모든 멤버를 접근할 수 있도록 자바가 자동으로 연결을 만들어주는 특성입니다.
언제 static을 쓸지 정확히 판단하기
내부 클래스가 외부 클래스의 데이터를 정말 사용하는지 여부를 판단하는 것이 초보자에게는 까다로울 수 있습니다.
내부 클래스가 외부 클래스의 특정 객체의 상태를 사용할 때만 non-static을 써야 합니다.
🗣️ 느낀점
처음에는 static과 non-static의 차이가 사소해 보였지만, 실제로는 메모리 관리와 코드 품질에 큰 영향을 준다는 것을 깨달았습니다.
특히 non-static 클래스가 의도치 않은 메모리 누수를 일으킬 수 있다는 사실이 놀라웠고, 클래스 설계 시 매우 신중해야겠다고 생각했습니다.
앞으로 클래스를 설계할 때는 항상 기본적으로 static을 사용하고, 꼭 외부 객체의 상태가 필요한 경우에만 non-static을 쓰는 습관을 들여야겠다고 느꼈습니다.
🎯 핵심 한 줄 정리 (Nutshell)
🔑 외부 클래스의 데이터를 쓰지 않는다면 멤버 클래스는 static으로 선언하여 메모리 효율과 코드의 유지보수성을 높이자!
Last updated