item 24 멤버 클래스는 되도록 static으로 만들라
중첩 클래스
- 다른 클래스 안에 정의된 클래스
- 중첩 클래스는 자신을 감싸는 외부 클래스에서만 사용되어야 하며, 그 외의 쓰임새가 있다면 톱레벨 클래스로 만들어야 한다.
- 종류 : 정적 멤버 클래스, 멤버 클래스, 익명 클래스, 지역 클래스
정적 멤버 클래스
- 바깥 클래스와 함께 쓰일 때 유용한
public
도우미 클래스로 쓰인다.
- 클래스 내부에서
static
으로 선언된 클래스이다.
- 외부 클래스의
private
멤버에도 접근이 가능하다.
- enum 타입도 암시적인
static
으로 간주할 수 있다.
public class OuterClass {
private static int outerStaticField = 10;
public static class StaticMemberClass {
private int innerField;
public StaticMemberClass(int innerField) {
this.innerField = innerField;
}
public void printFields() {
System.out.println("Outer static field: " + outerStaticField);
System.out.println("Inner field: " + innerField);
}
}
}
// 사용 예시
public class Main {
public static void main(String[] args) {
OuterClass.StaticMemberClass staticMemberInstance = new OuterClass.StaticMemberClass(20);
staticMemberInstance.printFields();
}
}
/* 출력 결과
Outer static field: 10
Inner field: 20
*/
(비정적) 멤버 클래스
- 정규화된
this
(class.this
)를 사용해 바깥 인스턴스의 메서드를 호출하거나 참조를 가져올 수 있다.
- 외부 인스턴스 없이는 생성이 불가능하다. 따라서, 멤버 클래스에서 외부 인스턴스에 접근할 일이 없다면 무조건 static을 붙여 정적 멤버 클래스로 생성하자.
- 외부 클래스와 멤버 클래스의 관계는 멤버 클래스가 인스턴스화될때 확립되며, 이후 더이상 변경할 수 없다.
- 어댑터를 정의할 때 자주 쓰인다. (클래스의
view
구현, 다른 타입의 인스턴스 리턴)
public class OuterClass {
private int outerField = 5;
public class MemberClass {
private int innerField;
public MemberClass(int innerField) {
this.innerField = innerField;
}
public void printFields() {
System.out.println("Outer field: " + outerField);
System.out.println("Inner field: " + innerField);
}
}
}
// 사용 예시
public class Main {
public static void main(String[] args) {
OuterClass outerInstance = new OuterClass();
OuterClass.MemberClass memberInstance = outerInstance.new MemberClass(15);
memberInstance.printFields();
}
}
/* 출력 결과
Outer field: 5
Inner field: 15
*/
익명 클래스
- 이름이 없고, 외부 클래스의 멤버도 아니다.
- 쓰이는 시점에 선언과 동시에 인스턴스가 생성되며, 어디서든 생성이 가능하다.
- non-static한 문맥에서 사용될 때만 바깥 클래스의 인스턴스를 참고할 수 있다.
- 이전에는 작은 함수 객체 혹은 처리 객체를 만들때 사용했다.(람다 나오고 나서는 잘 사용하지 않는 방식)
- 정적 팩터리 메서드 구현시에도 사용한다.
interface MyInterface {
void myMethod();
}
public class OuterClass {
public MyInterface createAnonymousClass() {
return new MyInterface() {
@Override
public void myMethod() {
System.out.println("Anonymous class method implementation");
}
};
}
}
// 사용 예시
class Main {
public static void main(String[] args) {
OuterClass outerInstance = new OuterClass();
MyInterface anonymousInstance = outerInstance.createAnonymousClass();
anonymousInstance.myMethod();
}
}
/* 출력 결과
Anonymous class method implementation
*/
익명 클래스의 제약 사항
- 선언한 지점에서만 인스턴스를 만들 수 있다.
instanceof
검사나 클래스의 이름이 필요한 작업은 수행할 수 없다.
- 익명 클래스를 사용하는 클라이언트는 그 익명 클래스가 상위 타입에서 상속한 멤버 외에는 호출할 수 없다.
- 여러 인터페이스를 구현할 수 없고, 인터페이스를 구현하는 동시에 다른 클래스를 상속할 수도 없다. (인터페이스의 이점이 사라진다.)
- 익명 클래스를 사용하는 클라이언트는 그 익명 클래스가 상위 타입에서 상속한 멤버 외에는 호출할 수 없다.
- 표현식이 중간에 등장하므로 코드가 길어지면 가독성이 떨어진다.
지역 클래스
- 네 중첩 클래스 중 가장 사용 빈도가 낮다.
- 지역 변수를 선언할 수 있다면 어디서든 선언할 수 있고, 유효 범위도 지역 변수와 같다.
- 멤버 클래스처럼 이름이 있고 반복해서 사용할 수 있다.
- 익명 클래스처럼 비정적 문맥에서 사용될 때만 바깥 인스턴스를 참조할 수 있다.
- 정적 멤버는 가질 수 없으며 가독성을 위해 짧게 작성해야 한다.
public class OuterClass {
private int outerField = 7;
public void outerMethod() {
class LocalClass {
private int localField;
public LocalClass(int localField) {
this.localField = localField;
}
public void printFields() {
System.out.println("Outer field: " + outerField);
System.out.println("Local field: " + localField);
}
}
// 메서드 내에서 지역 클래스 사용
LocalClass localInstance = new LocalClass(14);
localInstance.printFields();
}
}
// 사용 예시
class Main {
public static void main(String[] args) {
OuterClass outerInstance = new OuterClass();
outerInstance.outerMethod();
}
}
/* 출력 결과
Outer field: 7
Local field: 14
*/
Leave a comment