-
24. 멤버 클래스는 되도록 static으로 만들라JAVA/Effective java 2021. 3. 8. 14:19
- 중첩 클래스란 다른 클래스 안에 정의된 클래스를 말한다.
- 중첩 클래스의 종류는 정적 멤버 클래스, (비정적) 멤버 클래스, 익명 클래스, 지역 클래스, 이렇게 네 가지다.
- 이번 아이템에서는 각각의 중첩 클래스를 언제 그리고 왜 사용해야 하는지 이야기한다.
정적 멤버 클래스와 비정적 멤버 클래스
- 정적 멤버 클래스는 다른 클래스 안에 선언되고, 바깥 클래스의 private 멤버에도 접근할 수 있다는 점만 제외하고는 일반 클래스와 똑같다.
- 정적 멤버 클래스는 흔히 바깥 클래스와 함께 쓰일 때만 유용한 public 도우미 클래스로 쓰인다.
- 계산기가 지원하는 연산 종류를 정의하는 열거 타입을 예로 생각해보자. Operation 열거 타입은 Calculator 클래스의 public 정적 멤버 클래스가 되어야 한다.
- 그러면 Calculator 클라이언트에서 Calculator.Operation.PLUS 나 Calculator.Operation.MINUS 같은 형태로 원하는 연산을 참조할 수 있다. (아이템 34)
- 정적 멤버 클래스와 비정적 멤버 클래스의 구문상 차이는 단지 static이 붙어있고 없고 뿐이지만, 의미상 차이는 꽤 크다.
- 비정적 멤버 클래스의 인스턴스는 바깥 클래스의 인스턴스와 암묵적으로 연결된다.
- 따라서 비정적 멤버 클래스의 인스턴스 메서드에서 정규화된 this를 사용해 바깥 인스턴스의 메서드를 호출하거나 바깥 인스턴스의 참조를 가져올 수 있다.
- 정규화된 this란 클래스명.this 형태로 바깥 클래스의 이름을 명시하는 용법을 말한다.
- 따라서 개념상 중첩 클래스의 인스턴스가 바깥 인스턴스와 독립적으로 존재할 수 있다면 정적 멤버 클래스로 만들어야 한다.
- 비정적 멤버 클래스는 바깥 인스턴스 없이는 생성할 수 없기 때문이다.
- 비정적 멤버 클래스는 어댑터를 정의할 때 자주 쓰인다.
- 즉 어떤 클래스의 인스턴스를 감싸 다른 클래스의 인스턴스처럼 보이게 하는 뷰로 사용하는 것이다.
- 예컨대 Map 인터페이스의 구현체들은 보통 ( keySet, entrySet, values 메서드가 반환하는 ) 자신의 컬렉션 뷰를 구현할 때 비정적 멤버 클래스를 사용한다.
- 비슷하게 Set과 List 같은 다른 컬렉션 인터페이스 구현들도 자신의 반복자를 구현할 때 비정적 멤버 클래스를 주로 사용한다.
- 멤버 클래스에서 바깥 인스턴스에 접근할 일이 없다면 무조건 static을 붙여서 정적 멤버 클래스로 만들자.
- static 을 생략하면 바깥 인스턴스로의 숨은 외부 참조를 갖게 된다.
- 이 참조를 저장하려면 시간과 공간이 소비된다. 더 심각한 문제는 가비지 컬렉션이 바깥 클래스의 인스턴스를 수거하지 못하는 메모리 누수로 이어질 수 있다.
- Map 인스턴스를 예로 많은 Map 구현체는 키-값 쌍을 표현하는 엔트리 객체들을 가진다.
- 모든 엔트리가 맵과 연관되어 있지만 엔트리의 메서드들(getKey, getValue, setValue)는 맵을 직접 사용하지는 않는다.
- 따라서 엔트리를 비정적 멤버 클래스로 표현하는 것은 낭비고, private 정적 멤버 클래스가 가장 알맞다.
익명 클래스
- 익명 클래스는 멤버와 달리, 쓰이는 시점에 선언과 동시에 인스턴스가 만들어진다.
- 오직 비정적인 문맥에서 사용될 때만 바깥 클래스의 인스턴스를 참조할 수 있다.
- 익명 클래스는 표현식 중간에 등장하므로 짧지 않으면 가독성이 떨어진다.
- 자바가 람다를 지원한 이후로는 작은 함수 객체나 처리 객체는 람다를 이용해서 처리되고 있다.
지역 클래스
- 지역 클래스는 지역 변수를 선언할 수 있는 곳이면 실질적으로 어디서든 선언할 수 있고, 유효 범위도 지역변수와 같다.
- 다른 세 중첩 클래스와의 공통점도 하나씩 가지고 있다.
- 익명 클래스처럼 비정적 문맥에서 사용될 때만 바깥 인스턴스를 참조할 수 있으며, 정적 멤버는 가질 수 없고 가독성을 위해 짧게 작성해야 한다.
핵심 정리
- 중첩 클래스에는 네 가지가 있으며 각각의 쓰임이 다르다.
- 메서드 밖에서도 사용해야 하거나 메서드 안에 정의하기에 너무 길다면 멤버 클래스로 만든다.
- 멤버 클래스의 인스턴스 각각이 바깥 인스턴스를 참조한다면 비정적으로, 그렇지 않으면 정적으로 만들자.
- 중첩 클래스가 한 메서드 안에서만 쓰이면서 그 인스턴스를 생성하는 지점이 단 한 곳이고 해당 타입으로 쓰기에 적합한 클래스나 인터페이스가 이미 있다면 익명 클래스로 만들고, 그렇지 않다면 지역 클래스로 만들자.
참고 자료
'JAVA > Effective java' 카테고리의 다른 글
26. 로(Raw) 타입은 사용하지 말라 (0) 2021.03.11 25. 톱레벨 클래스는 한 파일에 하나만 담으라 (0) 2021.03.09 23. 태그 달린 클래스보다는 클래스 계층구조를 활용하라 (0) 2021.03.07 22. 인터페이스는 타입을 정의하는 용도로만 사용하라 (0) 2021.03.06 21. 인터페이스는 구현하는 쪽을 생각해 설계하라 (0) 2021.03.04