티스토리 뷰

* 이펙티브 자바 2/E를 읽고 공부하기 위해 기록한 게시글입니다.

 

22. 멤버 클래스는 가능하면 static으로 선언하라

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

- 중첩 클래스는 해당 클래스를 보조하는 용도로만 사용되어야 한다.

- 이외의 용도라면 중첩 클래스로 만들면 안된다.

- 총 4가지 종류가 있다.

1. 정적 멤버 클래스(static member class)

2. 비정적 멤버 클래스(nonstatic member class)

3. 익명 클래스(anonymous class)

4. 지역 클래스(local class)

 

1번을 제외한 나머지는 전부 내부 클래스(inner class)로 사용된다.

= "정적 멤버 클래스는 내부 클래스가 아니다" 

(외부 클래스의 객체가 없어도 내부 클래스의 객체 생성 가능 + 외부 클래스에서 static이 붙은 멤버만 접근 가능하기 때문)

 

(+) 중첩 클래스를 사용하는 이유

= 특정 클래스가 한 곳에서만 사용되는 경우, 클래스를 같이 묶어서 표현함으로써 캡슐화를 증가시키고 유지보수에 도움을 주기 위함.

 

 

1. 정적 멤버 클래스

정적 멤버 클래스는 가장 간단한 중첩 클래스다. 어쩌다 다른 클래스 안에 선언된 일반 클래스라고 생각해도 된다.

- 바깥 클래스의 모든 멤버에(private 포함) 접근할 수 있다.

(static이 붙은 멤버에만 접근 가능하다 -- 이외 멤버는 내부에서 외부 클래스의 객체를 생성해서 접근해야 한다)

- 바깥 클래스의 정적 멤버이며, 다른 정적멤버와 동일한 접근권한 규칙을 갖는다. (private, public 등...)

- 바깥 클래스와 함께 사용할 때만 유용한 helper class를 정의할 때 종종 사용된다.

- 그냥 언제든지 클래스를 그대로 들어내서 밖으로 분리할 수 있는 클래스라고 생각하면 되지 싶다.

 

* Helper Class

- Helper class는 말 그대로 특정 클래스의 작업에 도움을 주는 클래스이다. 보조적인 역할로만 사용된다.

- Utility class가 Helper class의 특별한 예시 중 하나이다. (모든 메서드가 static 메서드인 helper class)

- 유틸리티 클래스와 같이 여러 클래스에서 돌려쓰는 경우는 아예 별도의 클래스 파일을 만들어서 따로 분리하고, 한 클래스 내부에서만 사용되는 경우는 private 중첩 클래스로 만들어서 메서드의 비즈니스 로직이 잘 드러나게끔 해주는 용도? 라고 보면 될 것 같다.

public class OuterClass {

    private static int privateStaticOuterMember = 10;
    private int privateVal = 1;

    // 정적 멤버 클래스
    private static class Helper {
        public static void doHelp() {
            System.out.println("헬퍼가 도와줄게!");
        }
        // 바깥 클래스의 static 멤버는 private라도 접근 가능하다.
        public static int getPrivateStaticMember() {
            return privateStaticOuterMember;
        }
        // 바깥 클래스의 private 인스턴스 멤버도 접근 가능하다.
        public static int getPrivateMember() {
            // 외부 클래스의 인스턴스 멤버에 접근하려면 외부 객체를 먼저 만들어야 함
            OuterClass outerClass = new OuterClass();
            return outerClass.privateVal;
        }
    }

    public void outerMethod() {
        Helper.doHelp();
        System.out.println(Helper.getPrivateStaticMember());
        System.out.println(Helper.getPrivateMember());
    }
}
////
public class EffectiveJava {
    public static void main(String[] args) {
        OuterClass outerClass = new OuterClass();
        outerClass.outerMethod();
    }
}

Helper 내부 클래스를 private로 닫아놓으면 외부에서는 접근할 수 없고, public으로 열어놓으면 외부에서 접근할 수 있다.

// Helper 클래스를 static import 하면 앞에 붙은 OuterClass를 뗄 수 있다.
import springbook.effectivejava.OuterClass.Helper;

// 외부 객체 없이도 내부 객체를 생성할 수 있다.
Helper helper = new Helper();

근데 내 개인적인 생각으로는 private으로 닫아놓는게 좋지 않을까 싶다. 앞서 살펴봤다시피 중첩 클래스를 사용하는 이유가 캡슐화를 위해서인데 단지 보조적인 역할로 사용되는 헬퍼 클래스를 외부에서 접근한다는 것이 좋지는 않은 것 같다. 외부에서 접근할 거면 아예 유틸리티 클래스처럼 밖으로 빼는게 좋겠다.

 

 

 

2. 비정적 멤버 클래스

정적 멤버 클래스와 비정적 멤버 클래스의 차이는 멤버 클래스 앞의 static 유무 뿐이지만 이 둘은 사뭇 다르다.

- 비정적 멤버 클래스 객체는 바깥 클래스의 객체와 자동적으로 연결된다. 바깥 클래스의 메서드를 호출할 수도 있고, this 한정 구문을 통해 바깥 객체에 대한 참조를 획득할 수도 있다.

-> 중첩된 클래스의 객체가 바깥 클래스 객체와 독립적으로 존재할 수 있도록 하기 위해서는 중첩 클래스는 반드시 정적 멤버 클래스로 선언해야 한다. (비정적 멤버 클래스의 잭체는 바깥 클래스 객체 없이는 존재할 수 없다)

- 비정적 멤버 클래스 객체와 바깥 객체와의 연결은 비정적 멤버 클래스의 객체가 만들어지는 순간에 확립되고, 그 이후로는 변경할 수 없다. 바깥 클래스의 객체 메서드 안에서 비정적 멤버 클래스의 생성자가 호출되는 순간에 자동으로 만들어진다. 

-> 비정적 멤버 클래스 객체에는 이 연결을 위한 공간이 필요하며, 객체 생성시간이 늘어난다.

- "어댑터"를 정의할 때 자주 사용된다. 바깥 클래스 객체를 다른 클래스 객체인 것 처럼 보이게 하는 용도.

public class OuterClass {

    private int outerVal;

    void method() {
        System.out.println("Outer Class");
    }

    class InnerClass {
        void method() {
            // this 한정 구문
            OuterClass.this.method();
        }
        int getOuterVal() {
            // 외부 클래스의 멤버에 자유롭게 접근 가능.
            return outerVal;
        }
    }
}

public class EffectiveJava {
    public static void main(String[] args) {
        // 내부 클래스는 외부 클래스가 먼저 생성되고 난 이후에 생성 가능
        OuterClass outerClass = new OuterClass();
        OuterClass.InnerClass innerClass = outerClass.new InnerClass();
    }
}

- 외부 클래스.this 를 이용해 외부 클래스 멤버에 접근할 수 있다. (내부에서 this만 사용하는 경우는 내부 클래스 자신을 뜻한다)

 

 

(중간 요약)

바깥 클래스 객체에 접근할 필요가 없는 멤버 클래스를 정의할 때는 항상 static을 붙여 정적 멤버 클래스로 만들어라.

- 비정적 멤버 객체는 내부적으로 바깥 객체에 대한 참조를 유지하게 되어 시공간 요구량이 늘어나고, 바깥 객체에 대한 GC가 힘들어진다.  >> 메모리 누수의 위험이 있음 <<

- 또한 비정적 멤버 객체는 바깥 객체 없이는 생성할 수 없다는 문제점도 존재.

 

 

3. 익명 클래스

말 그대로 이름이 없다. 바깥 클래스의 멤버도 아니다.

- 멤버로 선언하지도 않으며, 사용하는 순간에 선언하고 객체를 만든다. 

- 표현식 문법을 준수하기만 하면 코드 어디에서나 사용할 수 있다.

- non-static context 안에서 사용될 때는 바깥 객체를 가질 수 있고, static context 안에서 사용되는 경우라도 static 멤버를 가질 수는 없다

- 여러 인터페이스를 구현할 수 없고, 클래스 이름이 필요한 곳에서는 사용할 수 없고, 인터페이스를 구현하는 동시에 클래스를 상속하는 것 또한 불가능하다. 

- 익명 클래스의 클라이언트는 상위 자료형에서 상속된 멤버만 호출할 수 있다.

- 익명 클래스는 표현식 중간에 등장하는 만큼 짧고 간결해야한다.

- 함수 객체 정의, 프로세스 객체 정의, 정적 팩토리 메서드 내부 등에서 자주 쓰인다.

 

 

4. 지역 클래스

지역 클래스는 4가지 중첩 클래스 중 사용 빈도가 제일 낮다.

- 지역 변수가 선언될 수 있는 곳 어디라면 선언할 수 있으며, 지역 변수와 동일한 유효범위 규칙을 갖는다.

(메서드 스택이 사라지면 클래스도 같이 사라진다)

- 다른 중첩 클래스와 마찬가지로 이름을 가지며, 반복적 사용이 가능하다. 

- 익명 클래스처럼 non-static context에 사용될 때만 바깥 객체를 가질 수 있고, static 멤버는 가질 수 없다.

- 역시 짧게 작성되야 한다. 

- 근데 굳이굳이 이런걸 쓸 필요가 있나 싶다. 개인적으로 가독성이 더 안좋아진 것 같기도 하다..;;

public class OuterClass {
    private int outerClassVal = 1;
    public void outerMethod() {
        int outerMethodVal = 2;
        class InnerClass {
            void method() {
                // 외부 클래스의 메서드 접근 가능
                System.out.println(outerClassVal);
                // 같은 메서드 스택에 있는 멤버 접근 가능
                System.out.println(outerMethodVal);
            }
        }
        InnerClass innerClass = new InnerClass();
        innerClass.method();
    }
}

 

(결론)

4가지의 중첩 클래스는 각각의 쓰임새가 있다.

 

* 중첩 클래스를 메서드 밖에서 사용할 수 있어야 하거나 메서드 안에 넣기에는 너무 길다

-> 멤버 클래스 (정적 / 비정적)

 

* 맴버 클래스 객체 각각이 바깥 객체에 대한 참조를 가져야 한다

그렇다) -> 비-정적 멤버 클래스

아니다) -> 정적 멤버 클래스

 

* 중첩 클래스가 특정한 메서드에 속하고, 오직 한 곳에서만 객체를 생성하며, 해당 중첩 클래스의 특성을 규정하는 타입

있다) -> 익명 클래스

없다) -> 지역 클래스

 

 

'JAVA' 카테고리의 다른 글

unchecked warning은 제거하라  (0) 2022.07.26
raw type은 사용하지 마라  (0) 2022.07.25
인터페이스를 활용해 전략 패턴을 사용하자  (0) 2022.07.25
추상 클래스 보다는 인터페이스  (0) 2022.07.24
기타  (0) 2022.07.24
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함