주제는 멤버 클래스는 되도록 static으로 만들어다 이고 자세히 살펴보자
우선, 멤버 클래스는 보통 중첩 클래스를 의미한다.
중첩 클래스란 클래스 내부에 정의된 클래스로 enum 클래스가 될 수도 있고 익명클래스가 될 수도 있다.
또한, 중첩 클래스는 자신을 감싼 클래스 안에서만 사용되어야 하고 그렇지 않다면 톱 클래스로써 사용되어야 한다.
중첩 클래스의 종류
- 정적 멤버 클래스
- 비정적 멤버 클래스
- 익명 클래스
- 지역 클래스
- 정적 멤버 클래스를 제외한 나머지 클래스는 inner class를 의미한다.
각각의 중첩 클래스를 언제 그리고 왜 사용하는지 알아보자
1. public static 멤버 클래스
특징
- 바깥 클래스의 private 멤버 접근 가능
- 바깥 클래스와 함께 쓰일때만 용이한 public 도우미 클래스로의 역할을 한다.
예시
- 아래와 같이 작성시 클라이언트는 계산기의 연산을 참조할 수 있다.
public class Calculator {
public static enum Operation {PLUS, MINUS, DIVIDE, TIMES};
}
추가적으로, public이나 protected 멤버는 사용을 주의해야한다. 릴리스 출시 후 공개 API가 되기 때문에 클라이언트 입장에서 변경이 어려워지기 때문이다.
2. private 정적 멤버 클래스
- 바깥 클래스가 표현하는 객체의 한부분으로 사용된다.
- 멤버 클래스의 메서드는 바깥 클래스의 메서드를 사용하지 않는다.
- 예시로는 HashMap의 Node 중첩 클래스가 있는데, 이는 Map의 Key-Value를 표현하는 Entry 인터페이스를 구현한 클래스이다.
Node 클래스는 해당 바깥 클래스의 메서드를 사용하지 않는다.
- HashMap의 Node 클래스는 HashMap 없이 사용가능하다.
3. 비정적 멤버 클래스
특징은 아래와 같다
// - 정적과의 구문상 차이는 static 뿐이지만 많이 다름
// - 바깥 클래스의 인스턴스와 암묵적 연결이 된다.
// -> 비정적 멤버 클래스의 인스턴스 메서드에서 정규화된 this를 이용해 바깥 클래스의 메서드를 호출하거나
// 인스턴스의 참조를 가져올 수 있다.
// -> 정규화된 this = 클래스명.this
// - 비정적 멤버 클래스와 바깥 클래스의 관계는 멤버클래스가 인스턴스화될 때, 확립되며 변경 불가능
// -> 바깥 클래스의 인스턴스 메서드에서 비정적 멤버 클래스의 생성자를 호출할 때 관계 형성
예시는 아래와 같다.
- HashMap의 KeySet 클래스는 HashMap 없이 존재가 없다.
// - Map 인터페이스의 구현체들은 보통 keySet 메서드와 같이 자신의 컬렉션 뷰를 구현할 때
// - Set, List 등도 자신의 반복자를 구현할 때 비정적 멤버 클래스를 주로 사용
// -> HashMap 에 들어가보면 keySet 라는 비정적 멤버 클래스가 정의되어 있다.
// -> 이는 HashMap 없이 독립적으로 사용할 수 없기에 비정적 멤버 클래스로 생성한 것이다.
정적 멤버 클래스 vs 비정적 멤버 클래스
위에서 본것 처럼 public 도우미로 사용되거나 바깥 클래스에서 정적 멤버 클래스의 메서드를 사용하지 않을 때 사용된다.
또한, 이러한 경우는 static을 설정해 굳이 인스턴스의 참조를 만들어 메모리와 시간 낭비를 하고 가비지 컬렉터에게 이상현상을 발생시키는 것을 막는다.
// - 중첩 클래스의 인스턴스가 바깥 인스턴스와 독립적으로 존재할 수 있다면 정적 멤버 클래스로 만들어야 한다.
// - 비정적 멤버 클래스는 바깥 인스턴스 없이는 생성될 수 없기 때문이다.
// - 멤버 클래스에서 바깥 인스턴스에 접근할 일이 없다면 무조건 static을 붙여서 정적 멤버 클래스로 만들자.
// - static 지정을 안한다면 바깥 클래스의 인스턴스로의 참조가 생기고 가비지 컬렉터가 수거시 문제가 될 수 있다.
"쉽게 설명하자면 서로 관계성이 적다면 static, 관계성이 있다면 non static으로 설정하자 여기서 말하는 관계성이란 해당 바깥 클래스 없이도 존재가 가능하는지에 대한 여부이다."
3. 익명 클래스
특징은 아래와 같다.
// - 바깥 클래스의 멤버는 아니다.
// - 쓰이는 시점에 선언과 동시에 인스턴스가 만들어진다.
// - 오직, 비정적인 문맥(static이 선언 안되어있는 메서드)에서 사용될때만 바깥 클래스의 인스턴스를 참조 가능하다.
// - 정적 문맥(static 선언 메서드)에서는 상수 변수 이외에 정적 멤버는 가질 수 없다.
// - 즉, 정적 문맥에서는 상수 표현을 위해 초기화된 final 기본 타입과 문자열 필드만 가질 수 있다.
// - 정적 문맥에서는 정적 추상 클래스를 통해 만든 익명 클래스만 사용 가능하다.
// - 제약 : 인스턴스를 선언한 지점에서만 생성가능하고, instanceOf 메서드 사용 불가능
// - 람다식을 주로 대신 사용
예시는 아래와 같다.
package Item24;
public class Outer {
private int outerField = 10;
private static final int outerField2 = 11;
public void doSomething1() {
Inner inner = new Inner() {
@Override
public void print() {
System.out.println("Inner field: " + outerField);
}
};
inner.print();
}
public static void doSomething2() {
Inner inner2 = new Inner() {
@Override
public void print() {
System.out.println("Inner field: " + outerField2);
}
};
inner2.print();
}
// 중첩 클래스 Inner
private abstract static class Inner {
public abstract void print();
}
}
4. 지역 클래스
특징은 아래와 같다.
// - 지역 변수처럼 사용
// - 멤버 클래스처럼 이름이 있어 반복해서 사용 가능
// - 익명 클래스처럼 비정적 문맥에서만 사용될 때 바깥 인스턴스를 참조할 수 있다.
// - 가독성을 위해 짧게 작성되어야 한다.
5. 정리
아래 맥락을 파악해 중첩 클래스의 종류를 달리하자.
// -> 바깥 클래스의 메서드 밖에서도 사용해야 하고 메서드 안에 정의하기 너무 길다면 멤버로 설정
// -> 멤버 클래스의 인스턴스 각각이 바깥 인스턴스를 참조한다면 비정적으로, 그렇지 않다면 정적으로 만들자
// -> 중첩 클래스가 한 메서드 안에서만 쓰이면서 그 인스턴스를 생성하는 지점이 단 한곳이고,
// 해당 타입으로 쓰기에 적합한 클래스나 인터페이스가 이미 있다면 익명 클래스로 만들고
// 그렇지 않다면 지역 클래스로 만들자.
코드
https://github.com/mokjaemin/EffectiveJAVA
'Language > Java Plus' 카테고리의 다른 글
Effective JAVA - Item25 : 톱 클래스 파일은 한 파일에 하나만 담아라 (0) | 2023.10.12 |
---|---|
Effective JAVA - Item23 : 태그 달린 클래스보다는 클래스 계층 구조를 활용하라 (0) | 2023.10.11 |
Effective JAVA - Item22 : 인터페이스는 타입을 정의하는 용도로만 사용하라 (0) | 2023.10.10 |
Effective JAVA - Item21 : 인터페이스는 구현하는 쪽을 생각해서 설계하라. (0) | 2023.10.09 |
Effective JAVA - Item20 : 추상 클래스보단 인터페이스를 우선시하라. (0) | 2023.10.09 |