ITEM 24 "멤버 클래스는 되도록 static 으로 만들어라"
클래스 내부에 변수처럼 중첩 클래스를 둘 수 있다. (nested class)
자신을 감싼 바깥 클래스에서만 쓸 수 있으며, 그게 아니라면 탑 레벨 클래스로 바꾸어야 한다. 이런 중첩 클래스는 정적 멤버 클래스, 비정적 멤버 클래스, 익명 클래스, 지역 클래스 4가지로 분류할 수 있다.
정적 멤버 클래스는 다른 클래스 안에 선언되고, 바깥 클래스의 private 멤버에도 접근할 수 있다. 그 이외에는 일반 클래스와 동일하다. 주로 바깥 클래스와 함께 쓰일 때만 사용하는 public 도우미 클래스로 쓰인다. 예를 들어 Calculator 클래스의 Operation.PLUS 와 MINUS 같은 형태로 연산을 참조할 때 정적 클래스로 표현하면 좋다.
public class EffectiveJavaTest {
private String name;
static class StaticClass {
void hello() {
EffectiveJavaTest effectiveJavaTest = new EffectiveJavaTest();
effectiveJavaTest.name = "gold-egg";
System.out.println(effectiveJavaTest.name);
}
}
public static void main(String[] args) {
EffectiveJavaTest.StaticClass staticClass = new StaticClass();
staticClass.hello();
}
}
비정적 멤버 클래스는 구문 상으로 static 키워드가 빠진 내부 클래스를 말한다. 비정적 멤버 클래스의 인스턴스가 바깥 클래스의 인스턴스와 암묵적으로 연결되어 있어서 비정적 멤버 클래스에서 "클래스명.this" 형태로 바깥 클래스 필드를 참조할 수 있다. 다른 클래스지만, 매우 강한 결합으로 묶일 수가 있다.
또 비정적 멤버 클래스는 바깥 인스턴스 없이 생성할 수 없다는 단점이 있다. 멤버 클래스가 인스턴스화 될 때 관계가 확립되며, 더 이상 변경할 수 없다. 아래 코드와 같이 바깥 클래스에서 new 로 생성하는데 진짜 드물게 바깥 인스턴스의 클래스.new MemberClass(args) 형태로 호출해 수동으로 만들기도 한다고 한다.
public class EffectiveJavaTest {
private final String name;
public EffectiveJavaTest(String name) {
this.name = name;
}
public String getName() {
NonStaticClass nonStaticClass = new NonStaticClass("noneStatic-class : ");
return nonStaticClass.getNameWithOuter();
}
private class NonStaticClass {
private final String noneStaticName;
public NonStaticClass(String noneStaticName) {
this.noneStaticName = noneStaticName;
}
public String getNameWithOuter() {
return noneStaticName + EffectiveJavaTest.this.name;
}
}
public static void main(String[] args) {
EffectiveJavaTest noneStaticClass = new EffectiveJavaTest("g-egg");
System.out.println(noneStaticClass.getName());
}
}
이런 비정적 멤버 클래스는 어댑터를 정의할 때 쓰인다. 어떤 클래스의 인스턴스를 감싸 마치 다른 클래스의 인스턴스처럼 보이게 하는 뷰로 사용할 때이다. Map 인터페이스의 keySet, entrySet, values 메서드가 반환하는 뷰들이 비정적 멤버 클래스를 사용한다고 한다. (열심히 찾아 보았지만 안 나온다... 하하)
장단점이 확실해서 멤버 클래스에서 바깥 인스턴스에 접근할 일이 없다면, 무조건 static 을 붙여 정적 멤버 클래스로 만들라고 권고하고 있다. 앞서 이야기 했듯이, 비정적 멤버 클래스는 숨은 외부 참조가 생겨 이 참조를 저장하면서 시간과 공간이 소비된다. 더 심각한 문제는 가비지 컬렉션이 바깥 클래스의 인스턴스를 수거하지 못 할 수도 있다. 언제까지 참조가 살아있는지 체크해야 되는데 눈에 보이지 않기 때문이다.
익명 클래스는 말 그대로 이름이 없고 바깥 클래스의 멤버가 아닌 클래스를 말한다. 쓰이는 시점에 선언과 동시에 인스턴스가 만들어지며 어디서든 만들 수 있다. 비정적인 문맥에서만 사용될 때 바깥 클래스의 인스턴스 참조가 가능하다.
public class EffectiveJavaTest {
private String name;
public void hello() {
HelloWorld helloBot = new HelloWorld() {
@Override
public void hello() {
System.out.println("g-egg");
}
};
helloBot.hello();
}
interface HelloWorld {
void hello();
}
}
선언한 지점에서만 인스턴스를 만들 수 있고, 여러 인터페이스를 구현할 수 없고 인터페이스를 구현하는 동시에 다른 클래스를 상속할 수도 없다. instanceof 검사나 클래스의 이름이 필요한 작업은 수행할 수도 없다. 익명 클래스를 사용하는 클라이언트는 그 익명 클래스가 상위 타입에서 상속한 멤버 외에는 호출할 수 없다. 가독성도 떨어진다.
자바가 람다를 지원하기 전에는 익명 클래스를 주로 사용했는데 이제는 이런 익명 클래스는 람다를 써도 된다. 이제는 정적 팩터리 메서드를 구현할 때 빼고 쓰지 않는다.
지역 클래스는 자주 쓰이지 않는다. 지역 변수를 선언할 수 있는 곳이라면 실질적으로 어디든 선언할 수 있다. 유효 범위도 지역 변수와 같다. 그런데, 지역 클래스를 쓰기 보다는 차라리 합성으로 객체를 변수로 사용하는 것이 좋지 않을까 생각한다.
public class EffectiveJavaTest {
public void hello() {
class LocalExample {
private String name;
public LocalExample(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
LocalExample localExample = new LocalExample("g-egg");
System.out.println(localExample.getName());
}
public static void main(String[] args) {
EffectiveJavaTest effectiveJavaTest = new EffectiveJavaTest();
effectiveJavaTest.hello();
}
}
"바깥 인스턴스를 참조하면, 비정적 클래스로 만들고 그렇지 않으면 정적 클래스로 만들자."
'독후감 > Effective JAVA' 카테고리의 다른 글
[Effective JAVA] 26 "로 타입은 사용하지 말라" (0) | 2022.05.23 |
---|---|
[Effective JAVA] 25 "탑 레벨 클래스는 한 파일에 하나만 담으라" (0) | 2022.05.21 |
[Effective JAVA] 23 "태그 달린 클래스보다는 클래스 계층구조를 활용하라" (0) | 2022.05.18 |
[Effective JAVA] 22 "인터페이스는 타입을 정의하는 용도로만 사용하라" (0) | 2022.05.17 |
[Effective JAVA] 21 "인터페이스는 구현하는 쪽을 생각해 설계하라" (0) | 2022.05.12 |