언어/Effective Java
이펙티브 자바 Item 24 - 멤버 클래스는 되도록 static으로 만들라
now0204
2024. 6. 4. 10:52
중첩 클래스는 다른 클래스 안에 정의된 클래스다. 중첩 클래스는 자신을 감싼 바깥 클래스에서만 쓰여야 하며, 그 외의 쓰임새가 있다면 톱레벨 클래스로 만들어야 한다.
중첩 클래스 종류
- 정적 멤버 클래스
- (비정적)멤버 클래스
- 익명 클래스
- 지역 클래스
정적 멤버 클래스를 제외한 나머지는 내부 클래스(inner class)다.
정적 멤버 클래스
- 클래스 내부에 static으로 선언된 클래스다.
- 다른 클래스 안에 선언되고, 바깥 클래스의 private 멤버에도 접근 가능. 그 외는 일반 클래스와 같다.
- private으로 선언 시 바깥 클래스에서만 접근 가능하다.
public class Animal {
private String name = "cat";
// 열거 타입도 암시적 static
public enum Kinds {
MAMMALS, BIRDS, FISH, REPTILES, INSECT
}
private static class PrivateSample {
private int temp;
public void method() {
Animal outerClass = new Animal();
System.out.println("private" + outerClass.name); // 바깥 클래스인 Animal의 private 멤버 접근
}
}
public static class PublicSample {
private int temp;
public void method() {
Animal outerClass = new Animal();
System.out.println("public" + outerClass.name); // 바깥 클래스인 Animal의 private 멤버 접근
}
}
}
- 바깥 클래스가 표현하는 객체의 한 부분(구성요소)일 때 사용
- example :Map의 Entry
- Map 안의 Entry는 interface다. Map을 구현하는 구현체인 HashMap에서 해당 interface를 implements하여 새로운 클래스를 정의한다.
- 멤버 클래스에서 바깥 인스턴스에 접근할 일이 없으면 무조건 static을 붙여서 정적 멤버 클래스로 만들자. static을 생략하면 바깥 인스턴스로의 숨은 참조가 생기고, 참조를 저장하기 위해 시간과 공간이 소비된다. 또한 가비지 컬렉션이 바깥 클래스의 인스턴스를 수거하지 못한다.
비정적 멤버 클래스
- static이 붙지 않은 멤버 클래스다.
- 비정적 멤버 클래스의 인스턴스는 바깥 클래스의 인스턴스와 암묵적으로 연결된다. 그래서 클래스명.this 형태로를 사용해 바깥 인스턴스의 메서드를 호출하거나 바깥 인스턴스의 참조를 가져올 수 있다.
- 바깥 인스턴스 없이 생성 불가능 하다.
따라서 개념상 중첩 클래스의 인스턴스와 바깥 클래스의 인스턴스가 독립적이라면 정적 멤버 클래스로 만들어야한다.
비정적 멤버 클래스의 인스턴스와 바깥 인스턴스 사이의 관계는 멤버 클래스가 인스턴스화 될 때 확립되며, 더 이상 변경할 수 없다. 이 관계는 비정적 멤버 클래스의 인스턴스 안에 만들어져 메모리 공간을 차지하며, 생성시간도 더 걸린다.
- 사용시기
- 비정적 멤버 클래스는 어댑터를 정의할 때 자주 쓰인다. 즉, 어떤 클래스의 인스턴스를 감싸 마치 다른 클래스의 인스턴스처럼 보이게 하는 뷰로 사용하는 것이다.
- example : Map 인터페이스의 구현체
- ketSet(),entrySet(),values()가 반환하는 자신의 컬렉션 뷰를 구현할 때 활용
*static을 생략하면 바깥 인스턴스로의 숨은 외부 참조를 갖게 되고 심각한 경우 GC가 바깥 인스턴스를 수거하지 못하는 경우가 발생한다. (메모리 누수)
익명 클래스
익명 클래스는 바깥 클래스의 멤버가 아니다.
사용되는 시점에 인스턴스화 되고, 어디서든 만들 수 있기 때문이다.
또한 익명 클래스는 비정적인 문맥에서만 사용될 때 바깥 클래스의 인스턴스를 참조할 수 있다.
상수 정적 변수 외에는 정적 변수를 가질 수 없다.
제약
- 선언한 지점에서만 인스턴스를 만들 수 있다.
- instanceof검사, 클래스의 이름이 필요한 작업을 수행할 수 없다.
- 여러 인터페이스를 구현할 수 없다
public class Calculator {
private int x;
private int y;
public Calculator(int x, int y) {
this.x = x;
this.y = y;
}
public int plus() {
Operator operator = new Operator() {
private static final String COMMENT = "더하기"; // 상수
// private static int num = 10; // 상수 외의 정적 멤버는 불가능
@Override
public int plus() {
// Calculator.plus()가 static이면 x, y 참조 불가
return x + y;
}
};
return operator.plus();
}
}
interface Operator {
int plus();
}
- 즉석에서 작은 함수 객체 처리할 때 주로 사용했다
- 람다가 대체함
- 정적 팩토리 메서드를 구현할 때 사용되기도 한다.
지역 클래스
지역 클래스는 지역변수를 선언할 수 있는 곳이라면 어디서든 선언할 수 있다.
유효 범위도 지역변수와 같다.
다른 중첩 클래스들의 공통점을 하나씩 가지고 있는데,
- 멤버 클래스처럼 이름을 가질 수 있고 반복해서 사용할 수 있다.
- 비정적 문맥에서 사용될 때만 바깥 인스턴스를 참조할 수 있다.
- 정적 멤버는 가질 수 없으며, 10줄 이하로 작성하는 편이 좋다.
public class LocalExample {
private int number;
public LocalExample(int number) {
this.number = number;
}
public void foo() {
// 지역변수처럼 선언해서 사용할 수 있다.
class LocalClass {
private String name;
public LocalClass(String name) {
this.name = name;
}
public void print() {
// 비정적 문맥에선 바깥 인스턴스를 참조 할 수 있다.
System.out.println(number + name);
}
}
LocalClass localClass = new LocalClass("local");
localClass.print();
}
}
참고자료
https://javabom.tistory.com/46