본문 바로가기

Dev Book Review/Effective Java

[Effective Java] item24. 멤버 클래스는 되도록 static으로 만들어라용도로만 사용하라

1. 중첩클래스란

중첩 클래스(nested class) : 다른 클래스 안에 정의된 클래스

  • 정적 멤버 클래스
  • (비정적) 멤버 클래스
  • 익명 클래스
  • 지역클래스

정적 멤버 클래스를 제외한 나머지는 내부 클래스라고 한다 (inner class)

 

2. 정적 멤버 클래스

일반 클래스와 다른점

  • 다른 클래스 안에 선언된다.
  • 바깥 클래스의 private 멤버에도 접근할 수 있다.

다른 정적 멤버와 똑같은 접근 규칙을 적용받는다.

바깥 클래스와 함께 쓰이는 public 도우미 클래스로 사용된다.

public class Calculator{
  public enum Operator{ // 열거타입도 정적 멤버 클래스이다
    PLUS(),MINUS()
  }
  public static class Operator{ // 대부분 이처럼 선언

  }
}

 

3. 비정적 멤버 클래스

정적 멤버 클래스와의 차이

  • 구문상 차이 : static이 있고 없고 차이
  • 의미상 차이 : 비정적 멤버 클래스의 인스턴스와 바깥 클래스의 인스턴스와 연결된다.
  • 정규화된 this를 이용해 바깥 인스턴스의 인스턴스 메서드 호출이 가능하다. 클래스명.this

개념상 중첩 클래스의 인스턴스가 바깥 클래스의 인스턴스와 독립적으로 존재할 수 있다면 정적 멤버 클래스로 만들어야한다.

비정적 멤버클래스는 바깥 인스턴스 없이는 생성할 수 없다.

public class NestedNonStaticExample {
    private final String name;

    public NestedNonStaticExample(String name) {
        this.name = name;
    }

    public String getName() {
        // 비정적 멤버 클래스와 바깥 클래스의 관계가 확립되는 부분
        NonStaticClass nonStaticClass = new NonStaticClass("nonStatic : ");
        return nonStaticClass.getNameWithOuter();
    }

    private class NonStaticClass {
        private final String nonStaticName;

        public NonStaticClass(String nonStaticName) {
            this.nonStaticName = nonStaticName;
        }

        public String getNameWithOuter() {
            // 정규화된 this 를 이용해서 바깥 클래스의 인스턴스 메서드를 사용할 수 있다.
            return nonStaticName + NestedNonStaticExample.this.getName();
        }
    }
}

비정적 멤버 클래스의 인스턴스와 바깥 인스턴스 사이의 관계는 멤버 클래스가 인스턴스화될 때 확립되며, 더이상 변경할 수 없다.

드물게 직접 [바깥 인스턴스의 클래스].new MebmerClass(agrs) 를 호출해 수동으로 만든다. 그러나 이 관계정보는 비정적 멤버 클래스의 인스턴스 안에 만들어져 메모리 공간을 차지하며, 생성시간도 더 걸린다.

NestedNonStaticExample nestedNonStaticExample = new NestedNonStaticExample("name");
nestedNonStaticExample.new NonStaticPublicClass();

 

비정적 멤버 클래스의 쓰임 : 어댑터의 역할

  • 어떤 클래스의 인스턴스를 감싸 마치 다른 클래스의 인스턴스처럼 보이게 하는 뷰로 사용한다.
  • Map 인터페이스의 구현체 : 컬렉션 뷰를 구현할 때 비정적 멤버 클래스를 사용
  • 컬렉션 인터페이스 구현체 : 자신의 반복자 구현

 

멤버 클래스에서 바깥 인스턴스에 접근할 일이 없다면 무조껀 static을 붙여서 정적 멤버클래스로 만들자

  • 숨은 외부 참조를 갖게되며, 이 참조를 저장하기 위한 시간과 공간이 소비된다.
  • GC가 바깥 클래스의 인스턴스를 수거하지 못하는 메모리 누수가 생길 수 있다.

 

4. 익명 클래스

  • 이름이 없다!
  • 바깥 클래스의 멤버도 아니다.
  • 쓰이는 시점에 선언과 동시에 인스턴스가 만들어진다.
  • 비정적인 문맥에서 사용될 때만 바깥 클래스의 인스턴스 참조가 가능하다
  • 상수 정적변수 (static final) 외에는 정적 변수를 가질 수 없다.
public class AnonymousExample {
    private double x;
    private double y;

    public double operate() {
        Operator operator = new Operator() {
            @Override
            public double plus() {
                System.out.printf("%f + %f = %f\n", x, y, x + y);
                return x + y;
            }
            @Override
            public double minus() {
                System.out.printf("%f - %f = %f\n", x, y, x - y);
                return x - y;
            }
        };
        return operator.plus();
    }
}

interface Operator {
    double plus();
    double minus();
}
 

익명 클래스의 제약사항

  • 선언한 지점에서만 인스턴스를 만들수 있다.
  • Instanced 검사, 클래스 이름이 필요한 검사를 할 수 없다.
  • 여러 인터페이스를 구현할 수 없다. 구현과 동시에 다른클래스 상속도 불가능하다.
  • 익명 클래스 사용 클라이언트는 사용하는 익명 클래스가 상위타입에서 상속한 멤버외에는 호출이 불가능하다
  • 표현식 중간에 등장해 10줄이 넘어가면 가독성이 좋지 않다.

사용 예시

  • 람다(자바7) 등장 이전에는 작은 함수 객체나 처리 객체 구현에 사용되었다 : 람다를 쓰자
  • 정적 팩터리 메서드를 구현할 때 사용되기 도한다.

 

5. 지역클래스

  • 가장 드물게 사용된다.
  • 지역변수를 선언할 수 있는 곳이면 어디서든 선언할 수 있다
  • 지역변수와 유효범위가 같다
  • 멤버 클래스 유사도 : 이름이 있고 반복해서 사용이 가능하다
  • 익명 클래스 유사도 : 비정적 문맥에서 사용할 때만 바깥 인스턴스 참조가 가능하다
  • 익명 클래스 유사도 : 정적 멤버는 가질 수 없고, 가독성을 위해 짧게 작성한다.
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();
    }
}