JAVA/Effective Java

item 24. 멤버 클래스는 되도록 static 으로 만든다.

Garonguri 2022. 5. 7. 16:04
728x90

중첩 클래스?

- 다른 클래스 안에 정의된 클래스

- 자신을 감싼 바깥 클래스에서만 사용되어야 한다. (그렇지 않을 경우 톱레벨로 만들어라)

 

중첩 클래스의 종류

  • 정적 멤버 클래스
    • 다른 클래스 안에 선언되고, 바깥 클래스의 private 멤버에 접근 가능하다.
    • 바깥 클래스와 함께 쓰일 때만 유용한 public 도우미 클래스로 쓰임
    • 개념상 중첩 클래스의 인스턴스가 바깥 인스턴스와 독립적으로 존재할 수 있다면 정적 멤버 클래스로 만들어야 한다.
    • 멤버 클래스에서 바깥 인스턴스에 접근할 일이 없다면 무조건 static을 붙여서 정적 멤버 클래스로 만들자.
      • static을 생략하면 바깥 인스턴스로부터 숨은 외부 참조를 갖게된다.
      • 해당 참조를 저장하려면 시간/공간이 소비되기도 하고, 가비지 컬렉션이 바깥 클래스 인스턴스를 수거하지 못해 메모리 누수가 생길 수 있다.
      • 참조가 눈에 보이지 않아 원인을 찾기도 어렵다.
    • private 정적 멤버 클래스는 흔히 바깥 클래스가 표현하는 객체의 한 부분을 나타낼 때 쓰인다.
    • 멤버 클래스가 공개된 클래스의 public 또는 protected라면, 정적 멤버클래스로 만들지 비정적 멤버클래스로 만들지가 굉장히 중요한 문제가 된다.
      • 멤버 클래스도 공개 API가 되므로 향후 릴리스에서 static을 붙이면 하위 호환성이 깨질 수 있다.
  • (비정적) 멤버 클래스 - 내부 클래스
    • 비정적 멤버 클래스의 인스턴스는 바깥 클래스의 인스턴스와 암묵적으로 연결된다.
    • 이 클래스의 인스턴스 메서드에서 정규화된 this를 사용해 바깥 인스턴스의 메서드를 호출하거나 바깥 인스턴스의 참조를 가져올 수 있다.
    • 정규화된 this? className.this 형태로 바깥 클래스 이름을 명시하는 용법
    • 바깥 인스턴스 없이는 생성할 수 없다.
    • 이 클래스의 인스턴스와 바깥 인스턴스 사이의 관계는 멤버 클래스가 인스턴스화 할 때 확립되며 변경할 수 없다.
    • Adapter(어댑터)를 정의할 때 자주 쓰인다.
    • 어떤 클래스의 인스턴스를 감싸 다른 클래스의 인스턴스처럼 보이게 하는 뷰로 사용한다.
      • Map 인터페이스의 구현체는 아래와 같이 (keySet, entrySet, value 메서드가 반환하는) 자신의 컬렉션 뷰를 구현할 때 비정적 멤버 클래스를 사용한다.
      • 비슷하게 Set과 List같은 다른 컬렉션 인터페이스 구현들도 자신의 반복자를 구현할 때 비정적 멤버 클래스를 주로 사용한다.
  • 익명 클래스 - 내부 클래스
    • 익명 클래스는 바깥 클래스 멤버가 아니다.
    • 따라서 쓰이는 시점에 선언과 동시에 인스턴스가 만들어진다.
    • 비 정적인 문맥에서 사용도리 때만 바깥 클래스의 인스턴스를 참조할 수 있다.
    • 정적 문맥에서는 상수 변수 이외의 정적 멤버는 가질 수 없다. 
      • 상수 표현을 위해 초기화된 final 기본 타입과 문자열 필드만 가질 수 있다.
    • 제약
      • 선언한 지점에서만 인스턴스를 만들 수 있다.
      • instanceof검사나 클래스 이름이 필요한 작업은 수행할 수 없다.
      • 여러 인터페이스를 구현할 수 없다.
      • 인터페이스를 구현하는 동시에 다른 클래스를 상속할 수 없다.
      • 클라이언트는 익명 클래스가 상위 타입에서 상속한 멤버 외에는 호출할 수 없다.
      • 표현식 중간에 등장해 가독성을 떨어트릴 수도 있다.
    • 람다 지원 전까지는 자주 쓰였다.
    • 현재는 정적 팩터리 메서드를 구현할 때 쓰인다.
//익명 클래스 예시
List<Integer> list = Arrays.asList(1,3,2,5,63,46,5,66,77,88,9,31,20);
Collections.sort(list, new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return Integer.compare(o1, o2);
    }
});

 

Collections.sort(list, (o1, o2) -> Integer.compare(o1, o2));
  • 지역 클래스 - 내부 클래스
    • 지역 변수를 선언할 수 있는 곳이면 어디서든 선언할 수 있다.
    • 유효 범위는 지역 변수와 같다.
    • 멤버 클래스처럼 이름이 있고 반복해서 사용할 수 있다.
    • 익명 클래스처럼 비정적 문맥에서 사용될 때만 바깥 인스턴스를 참조할 수 있다.
    • 정적 멤버를 가질 수 없다.
    • 가독성을 위해 짧게 작성한다.

 

[ 핵심 정리 ]

메서드 밖에서도 사용해야 하거나 메서드 안에 정의하기엔 너무 길다면 멤버 클래스로 만든다.
멤버 클래스의 인스턴스 각각이 바깥 인스턴스를 참조한다면 비정적으로, 그렇지 않으면 정적으로 만들자.
중첩 클래스가 한 메서드 안에서만 쓰이면서 그 인스턴스를 생성하는 지점이 단 한 곳이고 해당 타입으로 쓰기에 적합한 클래스나 인터페이스가 이미 있다면 익명 클래스로 만들고, 그렇지 않다면 지역 클래스로 만들자.
728x90