Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Item 24 & 25 & 26 멤버 클래스와 로 타입 #19

Open
ChaCha3088 opened this issue Dec 13, 2023 · 0 comments
Open

Item 24 & 25 & 26 멤버 클래스와 로 타입 #19

ChaCha3088 opened this issue Dec 13, 2023 · 0 comments
Assignees

Comments

@ChaCha3088
Copy link

section: 4장 & 5장

  • Item24 멤버 클래스는 되도록 static으로 만들라
  • Item25 톱레벨 클래스는 한 파일에 하나만 담으라
  • Item26 로 타입은 사용하지 말라

🍵 서론

멤버 클래스 & 로 타입

🌒 본론

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

💡 중첩 클래스(Nested Class)란 다른 클래스 안에 정의된 클래스를 말한다.

자신을 감싼 바깥 클래스에서만 쓰여야 하며, 그 외에 쓰임새가 있다면 톱레벨 클래스로 만들어야 한다.

종류

  • 정적 멤버 클래스
  • 비정적 멤버 클래스
  • 익명 클래스
  • 지역 클래스
    이 중 첫번째를 제외한 나머지는 내부 클래스(Inner Class)에 해당한다.

정적 멤버 클래스

💡 다른 클래스 안에 선언되고, 바깥 클래스의 private 멤버에도 접근할 수 있다는 점만 제외하고 일반 클래스와 똑같다.

예로, private으로 선언하면 바깥 클래스에서만 접근할 수 있다.
흔히 바깥 클래스와 함께 쓰일 때만 유용한 public 도우미 클래스로 쓰인다.
예로, 계산기가 지원하는 연산 종류를 정의하는 열거 타입을 예로 생각해보자(아이템 34)
Operation 열거 타입은 Calculator 클래스의 public 정적 멤버 클래스가 되어야 한다.
그러면 Calculator의 클라이언트에서 Calculator.Operation.PLUS 같은 형태로 원하는 연산을 참조할 수 있다.

정적 멤버 클래스 VS 비정적 멤버 클래스

💡 구문상 차이는 단지 static이 붙어 있는가 이지만, 의미상 차이는 꽤 크다.

비정적 멤버 클래스의 인스턴스는 바깥 클래스의 인스턴스와 암묵적으로 연결된다.
그래서 비정적 멤버 클래스의 인스턴스 메서드에서 정규화된 this를 사용해 바깥 인스턴스의 메서드를 호출하거나, 바깥 인스턴스의 참조를 가져올 수 있다.

정규화된 this

클래스명.this 형태로 바깥 클래스의 이름을 명시하는 용법을 말한다[JLS 15.8.4]

따라서 개념상 중첩 클래스의 인스턴스가 바깥 인스턴스와 독립적으로 존재할 수 있다면, 정적 멤버 클래스로 만들어야 한다. 비정적 멤버 클래스는 바깥 인스턴스 없이는 생성할 수 없기 때문이다.
비정적 멤버 클래스의 인스턴스와 바깥 인스턴스 사이의 관계는 멤버 클래스가 인스턴스화 될 때 확립되며, 더 이상 변경할 수 없다.
이 관계는 바깥 클래스의 인스턴스 메서드에서 비정적 멤버 클래스의 생성자를 호출할 때 자동으로 만들어지는게 보통이지만, 드물게는 직접 바깥 인스턴스의 클래스.new Member Class(args)를 호출해 수동으로 만들기도 한다. 예상하듯, 이 관계 정보는 비정적 멤버 클래스의 인스턴스 안에 만들어져 메모리 공간을 차지하며, 생성 시간도 더 걸린다.

비정적 멤버 클래스는 어댑터[Gamma95]를 정의할 때 자주 쓰인다.

💡 어떤 클래스의 인스턴스를 감싸 마치 다른 클래스의 인스턴스처럼 보이게 하는 뷰로 사용하는 것

예로, Map 인터페이스의 구현체들은 보통 keySet, entrySet, values 메서드가 반환하는 자신의 컬렉션 뷰를 구현할 때, 비정적 멤버 클래스를 사용한다.
비슷하게 Set과 List 같은 다른 컬렉션 인터페이스 구현들도 자신의 반복자를 구현할 때 비정적 멤버 클래스를 주로 사용한다.

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

static을 생략하면 바깥 인스턴스로의 숨은 외부 참조를 갖게 되어 시간과 공간이 소비된다. 더 심각한 문제는 GC가 바깥 클래스의 인스턴스를 수거하지 못하는 메모리 누수가 생길 수 있다.(아이템 7)
멤버 클래스가 공개된 클래스의 public이나 protected 멤버라면 정적이냐 아니냐는 더 중요해진다. 멤버 클래스 역시 공개 API가 되어 향후 릴리스에서 static을 붙이면 하위 호환성이 깨진다.

익명 클래스

멤버와 달리 쓰이는 시점에서 선언과 동시에 인스턴스가 만들어진다.
코드의 어디서든 만들 수 있다.
오직 비정적인 문맥에서 사용될 때만 바깥 클래스의 인스턴스를 참조할 수 있다.
정적 문맥에서라도 상수 변수 이외의 정적 멤버는 가질 수 없다.
즉 상수 표현[JLS 4.12.4]을 위해 초기화된 final 기본 타입과 문자열 필드만 가질 수 있다.

응용에 제약이 많음

선언한 지점에서만 인스턴스를 만들 수 있다.
instanceof 검사나 클래스의 이름이 필요한 작업은 수행할 수 없다.
여러 인터페이스를 구현할 수 없다.
다른 클래스를 상속할 수도 없다.
익명 클래스가 상위 타입에서 상속한 멤버 외에는 호출할 수 없다.
표현식 중간에 등장하므로 길면 가독성이 떨어진다.
Java가 람다를 지원하기 전에는 즉석에서 작은 함수 객체나 처리 객체(Process Object)를 만드는데 익명 클래스를 주로 사용했다.

정적 팩토리 메서드를 구현할 때 사용

코드 20-1 intArrayAsList 참조

package effectivejava.chapter4.item20;
import java.util.*;

// 코드 20-1 골격 구현을 사용해 완성한 구체 클래스
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];  // 오토박싱
            }

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

지역 클래스

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

정리

  • 비정적 멤버 클래스
    • 바깥 클래스가 존재해야 생성 가능
    • 바깥 클래스가 사용되지 않을 때 내부 클래스의 참조로 인해 GC가 수거할 수 없는 경우 발생 가능
  • 정적 멤버 클래스
    • 바깥 인스턴스와 독립적으로 존재
    • 바깥 클래스의 private 멤버에도 접근할 수 있다.
  • 따라서 내부 클래스가 독립적으로 사용된다면, 정적 멤버 클래스로 선언해서 사용하는 것이 좋다.

25. 톱레벨 클래스는 한 파일에 하나만 담으라

💡 컴파일러에 어느 소스 파일을 먼저 건네느냐에 따라 동작이 달라짐

굳이 여러 톱레벨 클래스를 한 파일에 담고 싶다면 정적 멤버 클래스(아이템 24)를 고려하라.

제네릭을 사용하면

컬렉션이 담을 수 있는 타입을 컴파일러에 알려주게 된다.
그래서 컴파일러는 알아서 형변환 코드를 추가할 수 있게 되고, 엉뚱한 타입의 객체를 넣으려는 시도를 컴파일 과정에서 차단하여 더 안전하고 명확한 프로그램을 만들어준다.

26. 로 타입은 사용하지 말라

용어 정리

클래스와 인터페이스 선언에 타입 매개 변수가 쓰이면, 이를 제네릭 클래스 혹은 제네릭 인터페이스라 한다.[JLS 8.1.2, 9.1.2]
예로, List 인터페이스는 원소의 타입을 나타내는 타입 매개변수 E를 받는다. 그래서 이 인터페이스의 완전한 이름은 List지만, 짧게 그냥 List라고도 자주 쓴다.

💡 제네릭 클래스와 제네릭 인터페이스를 통틀어 **제네릭 타입(Generic Type)**이라 한다.

각각의 제네릭 타입은 일련의 매개변수화 타입(Parameterized Type)을 정의한다[JLS 4.5]
클래스 혹은 인터페이스 이름이 나오고, 이어서 꺾쇠 괄호 안에 실제 타입 매개변수들을 나열한다.

제네릭 타입을 하나 정의하면 그에 딸린 로 타입(Raw Type)도 함께 정의된다.

로 타입이란 제네릭 타입에서 타입 매개변수를 전혀 사용하지 않을 때를 말한다.[JLS 4.8]

예로, List의 로 타입은 List다.

제네릭을 지원하기 전에는

컬렉션을 다음과 같이 선언했다.

private final Collection stamps = ...;

이 코드를 사용하면 실수로 Stamp 대신 Coin을 넣어도 아무 오류없이 컴파일되고 실행된다.

// 실수로 동전을 넣는다.
stamps.add(new Coint());

컬렉션에서 이 동전을 다시 꺼내기 전에는 오류를 알아채지 못한다.

💡 오류는 가능한 한 발생 즉시, 이상적으로는 컴파일할 때 발견하는 것이 좋다.

제네릭 타입을 쓰고 싶지만 실제 타입 매개변수가 무엇인지 신경 쓰고 싶지 않다면 물음표를 사용하자.

비한정적 와일드카드 타입인 Set<?>와 로 타입인 Set의 차이

  • 와일드카드 타입은 안전하다.
    • null 외에는 어떤 원소도 넣을 수 없다.
  • 로 타입은 안전하지 않다.

정리

로 타입을 사용하면 런타임에 예외가 일어날 수 있으니 사용하면 안된다.
제네릭 도입 이전 코드와 호환성을 위해 제공될 뿐이다.
Set는 어떤 타입의 객체도 저장할 수 있는 매개변수화 타입
Set<?>는 모종의 타입 객체만 저장할 수 있는 와일드카드 타입

🍃 결론

  • 멤버 클래스는 되도록 static으로 만들라
  • 톱레벨 클래스는 한 파일에 하나만 담으라
  • 로 타입은 사용하지 말라

reference

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant