Dev Book Review/Effective Java

[Effective Java] item24. 멤버 클래스는 되도록 static으로 만들어라용도로만 사용하라

1. 중첩클래스란 중첩 클래스(nested class) : 다른 클래스 안에 정의된 클래스 정적 멤버 클래스 (비정적) 멤버 클래스 익명 클래스 지역클래스 정적 멤버 클래스를 제외한 나머지는 내부 클래스라고 한다 (inner class) 2. 정적 멤버 클래스 일반 클래스와 다른점 다른 클래스 안에 선언된다. 바깥 클래스의 private 멤버에도 접근할 수 있다. 다른 정적 멤버와 똑같은 접근 규칙을 적용받는다. 바깥 클래스와 함께 쓰이는 public 도우미 클래스로 사용된다. public class Calculator{ public enum Operator{ // 열거타입도 정적 멤버 클래스이다 PLUS(),MINUS() } public static class Operator{ // 대부분 이처럼 선언..

[Effective Java] item24. 멤버 클래스는 되도록 static으로 만들어라용도로만 사용하라

728x90

1. 중첩클래스란

중첩 클래스(nested class) : 다른 클래스 안에 정의된 클래스

  • 정적 멤버 클래스
  • (비정적) 멤버 클래스
  • 익명 클래스
  • 지역클래스

정적 멤버 클래스를 제외한 나머지는 내부 클래스라고 한다 (inner class)

 

2. 정적 멤버 클래스

일반 클래스와 다른점

  • 다른 클래스 안에 선언된다.
  • 바깥 클래스의 private 멤버에도 접근할 수 있다.

다른 정적 멤버와 똑같은 접근 규칙을 적용받는다.

바깥 클래스와 함께 쓰이는 public 도우미 클래스로 사용된다.

public class Calculator{
  public enum Operator{ // 열거타입도 정적 멤버 클래스이다
    PLUS(),MINUS()
  }
  public static class Operator{ // 대부분 이처럼 선언

  }
}

 

3. 비정적 멤버 클래스

정적 멤버 클래스와의 차이

  • 구문상 차이 : static이 있고 없고 차이
  • 의미상 차이 : 비정적 멤버 클래스의 인스턴스와 바깥 클래스의 인스턴스와 연결된다.
  • 정규화된 this를 이용해 바깥 인스턴스의 인스턴스 메서드 호출이 가능하다. 클래스명.this

개념상 중첩 클래스의 인스턴스가 바깥 클래스의 인스턴스와 독립적으로 존재할 수 있다면 정적 멤버 클래스로 만들어야한다.

비정적 멤버클래스는 바깥 인스턴스 없이는 생성할 수 없다.

public class NestedNonStaticExample {
    private final String name;

    public NestedNonStaticExample(String name) {
        this.name = name;
    }

    public String getName() {
        // 비정적 멤버 클래스와 바깥 클래스의 관계가 확립되는 부분
        NonStaticClass nonStaticClass = new NonStaticClass("nonStatic : ");
        return nonStaticClass.getNameWithOuter();
    }

    private class NonStaticClass {
        private final String nonStaticName;

        public NonStaticClass(String nonStaticName) {
            this.nonStaticName = nonStaticName;
        }

        public String getNameWithOuter() {
            // 정규화된 this 를 이용해서 바깥 클래스의 인스턴스 메서드를 사용할 수 있다.
            return nonStaticName + NestedNonStaticExample.this.getName();
        }
    }
}

비정적 멤버 클래스의 인스턴스와 바깥 인스턴스 사이의 관계는 멤버 클래스가 인스턴스화될 때 확립되며, 더이상 변경할 수 없다.

드물게 직접 [바깥 인스턴스의 클래스].new MebmerClass(agrs) 를 호출해 수동으로 만든다. 그러나 이 관계정보는 비정적 멤버 클래스의 인스턴스 안에 만들어져 메모리 공간을 차지하며, 생성시간도 더 걸린다.

NestedNonStaticExample nestedNonStaticExample = new NestedNonStaticExample("name");
nestedNonStaticExample.new NonStaticPublicClass();

 

비정적 멤버 클래스의 쓰임 : 어댑터의 역할

  • 어떤 클래스의 인스턴스를 감싸 마치 다른 클래스의 인스턴스처럼 보이게 하는 뷰로 사용한다.
  • Map 인터페이스의 구현체 : 컬렉션 뷰를 구현할 때 비정적 멤버 클래스를 사용
  • 컬렉션 인터페이스 구현체 : 자신의 반복자 구현

 

멤버 클래스에서 바깥 인스턴스에 접근할 일이 없다면 무조껀 static을 붙여서 정적 멤버클래스로 만들자

  • 숨은 외부 참조를 갖게되며, 이 참조를 저장하기 위한 시간과 공간이 소비된다.
  • GC가 바깥 클래스의 인스턴스를 수거하지 못하는 메모리 누수가 생길 수 있다.

 

4. 익명 클래스

  • 이름이 없다!
  • 바깥 클래스의 멤버도 아니다.
  • 쓰이는 시점에 선언과 동시에 인스턴스가 만들어진다.
  • 비정적인 문맥에서 사용될 때만 바깥 클래스의 인스턴스 참조가 가능하다
  • 상수 정적변수 (static final) 외에는 정적 변수를 가질 수 없다.
public class AnonymousExample {
    private double x;
    private double y;

    public double operate() {
        Operator operator = new Operator() {
            @Override
            public double plus() {
                System.out.printf("%f + %f = %f\n", x, y, x + y);
                return x + y;
            }
            @Override
            public double minus() {
                System.out.printf("%f - %f = %f\n", x, y, x - y);
                return x - y;
            }
        };
        return operator.plus();
    }
}

interface Operator {
    double plus();
    double minus();
}
 

익명 클래스의 제약사항

  • 선언한 지점에서만 인스턴스를 만들수 있다.
  • Instanced 검사, 클래스 이름이 필요한 검사를 할 수 없다.
  • 여러 인터페이스를 구현할 수 없다. 구현과 동시에 다른클래스 상속도 불가능하다.
  • 익명 클래스 사용 클라이언트는 사용하는 익명 클래스가 상위타입에서 상속한 멤버외에는 호출이 불가능하다
  • 표현식 중간에 등장해 10줄이 넘어가면 가독성이 좋지 않다.

사용 예시

  • 람다(자바7) 등장 이전에는 작은 함수 객체나 처리 객체 구현에 사용되었다 : 람다를 쓰자
  • 정적 팩터리 메서드를 구현할 때 사용되기 도한다.

 

5. 지역클래스

  • 가장 드물게 사용된다.
  • 지역변수를 선언할 수 있는 곳이면 어디서든 선언할 수 있다
  • 지역변수와 유효범위가 같다
  • 멤버 클래스 유사도 : 이름이 있고 반복해서 사용이 가능하다
  • 익명 클래스 유사도 : 비정적 문맥에서 사용할 때만 바깥 인스턴스 참조가 가능하다
  • 익명 클래스 유사도 : 정적 멤버는 가질 수 없고, 가독성을 위해 짧게 작성한다.
public class LocalExample {
    private int number;

    public LocalExample(int number) {
        this.number = number;
    }

    public void foo() {
        // 지역변수처럼 선언해서 사용할 수 있다.
        class LocalClass {
            private String name;

            public LocalClass(String name) {
                this.name = name;
            }

            public void print() {
                // 비정적 문맥에선 바깥 인스턴스를 참조 할 수 있다.
                System.out.println(number + name);
            }
        }
        LocalClass localClass = new LocalClass("local");
        localClass.print();
    }
}

댓글

Comments