본문 바로가기

Language/Java Plus

Effective JAVA - Item24 : 멤버 클래스는 되도록 static으로 만들어라

 

 

 

주제는 멤버 클래스는 되도록 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

 

GitHub - mokjaemin/EffectiveJAVA: Study Files Of Effective JAVA

Study Files Of Effective JAVA. Contribute to mokjaemin/EffectiveJAVA development by creating an account on GitHub.

github.com