[교재 EffectiveJava] 아이템 24. 멤버 클래스는 되도록 static으로 만들라

반응형
728x90
반응형

중첩 클래스

중첩 클래스(nested class)란 다른 클래스 안에 정의된 클래스를 말한다. 중첩된 클래스는 자신을 감싼 바깥 클래스에서만 쓰여야하며, 그 외의 쓰임새가 있다면 톱레벨 클래스로 만들어야한다.

 

중첩 클래스의 종류

1) 정적 멤버 클래스

  • 바깥 클래스와 함께 쓰일때만 유용한 public 도우미 클래스

2) (비정적) 멤버 클래스

  • 바깥 클래스의 인스턴스와 암묵적으로 연결된다.
  • 어댑터를 정의할때 자주 쓰인다.
  • 멤버 클래스에서 바깥 인스턴스를 참조할 필요가 없다면 무조건 정적 멤버 클래스로 만들자.

3) 익명 클래스

  • 바깥 클래스의 멤버가 아니며, 쓰이는 시점과 동시에 인스턴스가 만들어진다.
  • 비정적인 문맥에서 사용될 때만 바깥 클래스의 인스턴스를 참조할 수 있다.
  • 자바에서 람다를 지원하기 전에 즉석에서 작은 함수 객체나 처리 객체를 만들때 사용했다.
  • 정적 팩터리 메서드를 만들때 사용할 수도 있다.

4) 지역 클래스

  • 가장 드물게 사용된다.
  • 지역 변수를 선언하는 곳이면 어디든 지역 클래스를 정의해 사용할 수 있다.
  • 가독성을 위해 짧게 작성해야한다.

1)번의 정적 멤버 클래스를 제외한 나머지는 중첩클래스(내부클래스)에 해당한다.

 

정적 멤버 클래스

정적 멤버 클래스는 다른 클래스 안에 선언되고, 바깥 클래스의 private 멤버에서도 접근할 수 있다는 점을 제외하고는 일반 클래스와 동일하다. 정적 멤버 클래스는 단지 static 이 붙어있을 뿐이지만 의미상의 차이는 크다. 비정적 멤버 클래스(inner class)의 인스턴스는 바깥 클래스의 인스턴스와 암묵적으로 연결된다. 내부 클래스의 인스턴스 메서드에서 정규화된 this 를 사용하여 바깥 인스턴스의 메서드를 호출하거나 인스턴스의 참조를 가져올 수 있다.

 

정규화된 this

클래스명.this 형태로 바깥 클래스의 이름을 명시하는 용법

 

 

바깥 클래스의 인스턴스 생성

중첩 클래스의 인스턴스가 바깥 인스턴스와 독립적으로 존재할 수 있다면 정적 멤버 클래스로 만들어야한다. 비정적 멤버 클래스는 바깥 인스턴스 없이는 생성할 수 없기 때문이다. 비정적 멤버 클래스의 인스턴스와 바깥 인스턴스 사이의 관계는 멤버 클래스가 인스턴스화될 때 확립되며, 더이상 변경할 수 없다.

public class OutterClass {
    private int number = 10;

    void printNumber() {
        InnerClass innerClass = new InnerClass();
    }

    private class InnerClass {
        void doSomething() {
            System.out.println(number);
            OutterClass.this.printNumber();
        }
    }

    public static void main(String[] args) {
        InnerClass innerClass = new OutterClass().new InnerClass();
        innerClass.doSomething();
    }
}

 

바깥 클래스의 인스턴스 메서드에서 비정적 멤버 클래스의 생성자를 호출할때 자동으로 만들어지는게 보통이지만, 드물게는 직접 바깥 인스턴스의 클래스.newMemberClass(Args) 를 호출해 수동으로 만들기도 한다. 이 관계 정보는 비정적 멤버 클래스의 인스턴스 안에 만들어져 메모리 공간을 차지하며, 생성 시간도 더 걸린다.

import java.util.AbstractSet;
import java.util.Iterator;

public class MySet<E> extends AbstractSet<E> {
    @Override
    public Iterator<E> iterator() {
        return new MyIterator();
    }

    @Override
    public int size() {
        return 0;
    }

    /**
     * 비정적 멤버 클래스 
     */
    private class MyIterator implements Iterator<E> {

        @Override
        public boolean hasNext() {
            return false;
        }

        @Override
        public E next() {
            return null;
        }
    }
}

 

 

숨은 외부 참조

public class MySet<E> extends AbstractSet<E> {
    @Override
    public Iterator<E> iterator() {
        return new MyIterator();
    }
    
    ...
}

 

멤버 클래스에서 바깥 인스턴스에 접근할 일이 없다면 무조건 static 을 붙여서 정적 멤버 클래스로 만들자. static 을 생략하면 바깥 인스턴스로의 숨은 외부 참조를 갖게 된다. 이 참조를 저장하려면 시간고 공간이 소비된다. 더 심각한 문제는 가비지 컬렉션이 바깥 클래스의 인스턴스를 수거하지 못하는 메모리 누수가 생길수도 있다는 점이다. 참조가 눈에 보이지 않으니 때때로 심각한 상황이 발생하기도 한다.

 

 

private 정적 멤버 클래스

private 정적 멤버 클래스는 바깥 클래스가 표현하는 객체의 한 부분을 나타낼때 쓴다. 멤버 클래스가 공개된 클래스의 public이나 protected 멤버라면 정적이냐 아니냐는 더욱 더 중요해진다. 멤버 클래스 역시 공개 API가 되니, 혹시라도 향후 릴리스에서 static 을 붙이면 하위 호환성이 깨진다. 

 

 

익명클래스

이름이 없는 클래스이고, 바깥 클래스의 멤버도 아니다. 쓰이는 시점에 선언과 동시에 인스턴스가 만들어지며 어디서든 만들 수 있다. 비정적인 문맥에서 사용될 때만 바깥 클래스의 인스턴스를 참조할 수 있다. 정적 문맥에서라도 상수 변수 이외의 정적 멤버는 가질 수 없다. 선언한 지점에서만 인스턴스를 만들 수 있고 instanceof 검사나 클래스의 이름이 필요한 작업은 수행할 수 없다. 여러 인터페이스를 구현할 수 없고 인터페이스를 구현하는 동시에 다른 클래스를 상속할 수도 없다. 익명 클래스를 사용하는 클라이언트는 그 익명 클래스가 상위 타입에서 상속한 멤버 외에는 호출할 수 없다. 

 

 

지역클래스

지역 변수를 선언할 수 있는 곳이면 실직적으로 어디서든 선언할 수 있고, 유효 범위도 지역변수와 같다. 멤버 클래스처럼 이름이 있고 반복해서 사용할 수 있다. 익명 클래스처럼 비정적 문맥에서 사용될 때만 바깥 인스턴스를 참조할 수 있다. 정적 멤버는 가질 수 없고 가독성을 위해 짧게 작성해야한다.

public class MyClass {
    private int number = 10;

    void doSomething() {
    	// 지역클래스 
        class LocalClass {
            private void printNumber() {
                System.out.println(number);
            }
        }

        LocalClass localClass = new LocalClass();
        localClass.printNumber();
    }

    public static void main(String[] args) {
        MyClass myClass = new MyClass();
        myClass.doSomething();
    }
}

 

 

반응형

Designed by JB FACTORY