본문 바로가기
개발관련 서적 정리/Effective Java

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

by Backchus 2023. 2. 5.

OutterClass내부의 정적 멤버 클래스

package me.whiteship.chapter04.item24.staticmemberclass;

public class OutterClass {

    private static int number = 10;

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

InnerClass는 OutterClass의 내부에 있는 static 멤버 클래스 입니다. static 멤버 클래스인 InnerClass의 특징은 number와 같은 바깥에 있는 정적 필드에 접근을 할 수 있습니다. 또한 바깥클래스인 OutterClass를 필요로 하지 않고 독립적입니다.

        InnerClass innerClass = new InnerClass();
        innerClass.doSomething();

OutterClass필요없이 InnerClass인스턴스를 단독으로 사용이 가능합니다. 이런 static 멤버 클래스는 바깥클래스인 OutterClass를 거쳐서 쓰기 용이한 클래스를 정의할때 static 멤버 클래스로 선언합니다.

OutterClass내부의 비정적 멤버 클래스

package me.whiteship.chapter04.item24.memberclass;

public class OutterClass {

    private int number = 10;

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

비정적 멤버 클래스인 InnerClass는 암묵적으로 OutterClass의 인스턴스에 대한 참조가 생깁니다. 그 뜻은 OutterClass의 인스턴스 없이는 InnerClass인스턴스를 생성할 수 없다는 뜻입니다. 그래서 InnerClass인스턴스를 만들때 반드시 OutterClass인스턴스를 만든후 생성해야 합니다.

        InnerClass innerClass = new OutterClass().new InnerClass();
        innerClass.doSomething();

하지만 이렇게 만들어쓰는 경우는 드뭅니다. 대부분 InnerClass는 OutterClass의 메서드안에서 직접 만들어서 쓰기 때문입니다.

package me.whiteship.chapter04.item24.memberclass;

public class OutterClass {

    void printNumber() {
        InnerClass innerClass = new InnerClass();    // 메서드에서 InnerClass를 생성
    }

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

그리고 InnerClass가 OutterClass의 인스턴스를 암묵적으로 참조한다고 했는데 그 참조를 접근하려면 아래와같이 쓸 수 있습니다.

package me.whiteship.chapter04.item24.memberclass;

public class OutterClass {

    // 코드 생략

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

OutterClass.this가 인스턴스기 때문에 인스턴스가 가지고있는 모든 멤버에 접근이 가능합니다. 이렇게 어떠한 내부클래스가 바깥클래스에 대한 인스턴스를 참조하는 경우가 많으면 비정적 멤버 클래스로 접근하는 경우가 맞습니다. 하지만 따로 인스턴스를 참조하는 것이 없는경우 굳이 비정적 클래스로 선언할 필요는 없습니다. 왜냐면 불필요하게 OutterClass의 인스턴스를 참조하기 때문입니다. 시간적으로나 공간적으로나 비효율적입니다.

비정적 멤버클래스 선언이 유용한경우는 대표적인예로 어댑터 클래스를 들수 있습니다.

package me.whiteship.chapter04.item24.memberclass;

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

}

어댑터 패턴은 클래스타입을 우리가 원하는 타입으로 변환시켜주는 역할을 합니다. 우리가 원하는 MyIterator구현체가 있다면 그 구현체를 내부적으로 구현하고 해당하는 MyIterator클래스를 Iterator타입으로 쓸 수 있게끔 할때 비정적 멤버 클래스가 자주 사용됩니다.

익명클래스

package me.whiteship.chapter04.item24.anonymousclass;

import java.util.AbstractList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

// 코드 20-1 골격 구현을 사용해 완성한 구체 클래스 (133쪽)
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;
            }
        };
    }
}

어떤 인스턴스를 만들어 줄 때 굳이 클래스를 정의해서 만들어줄 필요없이 정의함과 동시에 인스턴스를 생성해줍니다. 이름이 없는 인스턴스지만 클래스의 정의임과 동시에 인스턴스를 생성합니다. 위와같이 익명클래스를 선언하는 방법은 자바8이전 람다가 도입되기전에 많이 사용되었던 방법이었습니다. 자바8 이후는 람다를 이용하거나 메서드참조를 통해 익명클래스를 생성합니다.

로컬 클래스

package me.whiteship.chapter04.item24.localclass;

public class MyClass {

    private int number = 10;

    void doSomething() {
        class LocalClass {
            private void printNumber() {
                System.out.println(number);
            }
        }

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

로컬클래스인 LocalClass는 로컬 변수와 마찬가지로 멤버는 아니고 doSomething메서드의 로컬 클래스입니다. 로컬클래스라서 익명클래스와는 다르게 이름을 가질 수 있고 이름을 가진상태로 만들어서 인스턴스를 만들면서 사용하면 되는데 잘 쓰이진 않습니다. 이렇게 쓰면 메서드가 길어질 가능성이 크고 길진 않지만 제가 회사에서 개발을하면서 메서드내부에 로컬 클래스를 선언해서 쓰는 경우는 한번도 보지 못했습니다.