본문 바로가기
개발 공부/Java

이펙티브 자바 아이템 24 - 멤버 클래스는 되도록 static 으로 만들라 - 핵심 정리

by 개발인생 2022. 12. 2.
반응형

아이템 24 - 멤버 클래스는 되도록 static 으로 만들라 - 핵심 정리

이 글은 백기선 님의 이펙티브 자바 강의와 이펙티브 자바 3 / E 편을 참고하여 작성하였습니다.

멤버 클래스란 클래스를 구성하는 요소이다.

클래스를 구성하는 요소에는 필드, 메서드들 이 있다.

클래스도 이것과 같은 급으로 정의가 되어있다면 멤버 클래스 이다.

멤버는 클래스의 구성요소를 의미한다.

중첩 클래스는 어느 한 클래스의 내부 어딘가에 또다른 클래스가 정의되어 있다면 중첩 클래스 이다.

모든 중첩 클래스는 멤버 클래스라고 생각되기 쉽지만 그건 아니다.

변수를 예로들면 변수가 정의된 위치에 따라 멤버 변수, 로컬 변수 등으로 나뉜다.

어떠한 scope 이 메서드에 포함되어 있으면 로컬 변수 라고 부른다.

scope 이 클래스 범위라면 전부 멤버 이다.

때문에 중첩 클래스 이지만 멤버가 아닌 클래스 가 존재한다.

총 네 종류의 중첩 클래스가 있다.

이 아이템은 되도록이면 비정적 멤버 클래스 대신 정적 멤버 클래스 를 사용하라는게 핵심이다.


정적 멤버 클래스

public class OutterClass {

    private static int number = 10;

    static private class InnerClass {
        void doSomething() {
            System.out.println(number);
        }
    }

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

    }
}

OutterClass 안에 static 으로 정의되어있는 InnerClass 클래스가 있다.

정의되어 있는 scope 이 클래스 범위로 되어있다.

InnerClass 를 정적 멤버 클래스 라고 한다.

정적 멤버 클래스 의 특징은 자신을 감싸고 있는 클래스의 정적 멤버 변수 에 접근 할 수 있다는 것이다.

또 다른 특징으로는 감싸고 있는 클래스의 인스턴스 를 필요로 하지 않는다는 것이다.

독립적으로 쓰이기 보다는 바깥 클래스와 함께 사용할 때 유용할 때 사용한다.

비정적 멤버 클래스

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();
    }

}

비정적 멤버 클래스는 static 이 없는 중첩 클래스를 말한다.

비정적 멤버 클래스는 암묵적으로 바깥 클래스의 참조 가 생긴다.

즉, 자신을 감싸고 있는 클래스의 인스턴스 에 대한 참조가 생긴다는 뜻이다.

비정적 멤버 클래스는 자기 자신을 감싸고 있는 클래스의 인스턴스 없이는 자기 자신을 생성할 수 없다.

비정적 멤버 클래스의 인스턴스를 만들기 위해서는

new OutterClass().new InnerClass();

이렇게 자기 자신을 감싸고 있는 클래스의 인스턴스를 먼저 생성해야한다.

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

보통은 바깥 클래스의 어떤 메서드안에서 직접 만들어 사용하게 된다.

public class OutterClass {


    private class InnerClass {
       private int number = 10;


       void doSomething() {
            System.out.println(number);
        }
    }

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

만약 바깥 클래스의 멤버를 참조하고 있지 않다면 비정적 멤버 클래스에 적합한 경우가 아니다.

시간적으로나 공간적으로나 비효율 적이다.

이런 경우에는

public class OutterClass {

    private static class InnerClass {
       private int number = 10;


       void doSomething() {
            System.out.println(number);
        }
    }

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

static 을 붙여 InnerClass 클래스의 인스턴스만 만들어 사용하게 만들어야한다.

어댑터 패턴은 비정적 멤버 클래스 가 유용하게 사용되는 경우이다.

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 IntArrays {
    static List<Integer> intArrayAsList(int[] a) {
        Objects.requireNonNull(a);

        // 다이아몬드 연산자를 이렇게 사용하는 건 자바 9부터 가능하다.
        // 더 낮은 버전을 사용한다면 <Integer>로 수정하자.
        return new AbstractList<>() { // 익명 클래스
            @Override public Integer get(int i) {
                return a[i];  // 오토박싱(아이템 6)
            }

            @Override public Integer set(int i, Integer val) {
                int oldVal = a[i];
                a[i] = val;     // 오토언박싱
                return oldVal;  // 오토박싱
            }

            @Override public int size() {
                return a.length;
            }
        };
    }

    public static void main(String[] args) {
        int[] a = new int[10];
        for (int i = 0; i < a.length; i++)
            a[i] = i;

        List<Integer> list = intArrayAsList(a);
        Collections.shuffle(list);
        System.out.println(list);
    }
}

익명 클래스는 정의함과 동시에 인스턴스를 만들 수 있지만 인스턴스의 이름은 없다.

         return new AbstractList<>() { // 익명 클래스
            @Override public Integer get(int i) {
                return a[i];  // 오토박싱(아이템 6)
            }

            @Override public Integer set(int i, Integer val) {
                int oldVal = a[i];
                a[i] = val;     // 오토언박싱
                return oldVal;  // 오토박싱
            }

            @Override public int size() {
                return a.length;
            }
        };

위처럼 new 를 사용하고 그 뒤에 클래스를 정의한다.

클래스를 정의함과 동시에 인스턴스를 만드는 것이다.

이름이 없는 인스턴스지만 그 자리에서 그대로 만들어 사용할 수 있다.

이러한 형태는 자바 8람다 가 추가되기 전에 많이 사용되었다.

이제는 익명 클래스 대신 람다메서드 레퍼런스 로 많이 대체되고 있다.

지역 클래스

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();
    }
}

로컬 클래스는 로컬 변수 와 마찬가지로 멤버 가 아니다.

위의 LocalClass 는 doSomething 메서드의 로컬 클래스 이다.

로컬 클래스라서 인스턴스의 이름을 가질 수 있지만 잘 쓰이질 않는다.


이렇게 중첩 클래스 4가지를 살펴보았다.

여기서 중요한건 정적 멤버 클래스비정적 멤버 클래스 이고,

이 중에서도 가급적이면 멤버 클래스의 바깥 클래스의 인스턴스에 대한 참조가 없다면

정적 멤버 클래스 로 만들어서 좀 더 효율적인 클래스로 만드는 걸 권장한다.

반응형

댓글