peaches-book-study / effective-java

이펙티브 자바 3/E
0 stars 2 forks source link

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

Open hyunsoo10 opened 6 months ago

hyunsoo10 commented 6 months ago

Chapter : 5. 제네릭

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

Assignee : hyunsoo10


🍑 서론

클래스와 인터페이스 선언에 타입 매개변수가 쓰이면, 이를 제네릭 클래스 혹은 제네릭 인터페이스라 한다. 그리고 제네릭 클래스와 제네릭 인터페이슬들 통틀어 제네릭 타입이라 한다.

제네릭 타입을 하나 정의하면 그에 딸린 raw type도 함께 정의된다. raw type이란 제네릭 타입에서 매개 변수를 전혀 사용하지 않을 때를 말한다.

🍑 본론


    //⚠️ 제네릭을 지원 하기 이전에는 컬렉션을 다음과 같인 선언했다.
    private  Collection stamps;

    //⚠️위 처럼 컬렉션을 사용하면 Stamp 대신 Coin을 넣어도 컴파일 오류 없이 실행된다.
    //⚠️런타임에야 오류를 알아챌 수 있을 것이다.
    stamps.add(new Coin());

    //✅ 이렇게 선언하면 stamps에는 Stamp 인스턴스만 넣어야 함을 컴파일러가 인지하게 된다. 
    private  Collection<Stamp> stamps;

📌 컴파일러는 컬렉션에서 원소를 꺼내는 모든 곳에서 보이지 않는 형변환을 추가하여 절대 실패하지 않음을 보장한다.

📌raw type(타입 매개변수가 없는 제네릭 타입)을 쓰는걸 언어 차원에서 막아 놓지는 않았지만 절대 사용하지 말아라. raw type을 사용하면 제네릭이 안겨주는 안정성과 표현력을 모두 잃게된다.

✏️그렇다면 애초에 왜 raw type을 만들어 놓은 이유는? -> 호환성 떄문에 😓

자바가 제네릭을 받아들이기 까지 거의 10년이 걸린 탓에 제네릭 없이 짠 코드가 이미 세상을 뒤덮어 버렸다. 그래서 기존 코드를 모두 수용하면서 제네릭을 사용하는 새로운 코드와도 맞물려 돌아가게해야만 헀다.

✏️ListList<Object>에는 무슨 차이가 있을까?

List는 raw type으로 제네릭 타입에서 완전히 발을 뺀 것이고, List<Object>는 모든 타입을 허용한다는 의사를 컴파일러에 명확히 전달한 것이다.


📍List<String>은 raw type인 List의 하위 타입이지만, List<Object>의 하위 타입은 아니다(아이템 28)

🧑‍💻example

    public static void main(String[] args) {
        List<String> strings = new ArrayList<>();

        unsafeAdd(strings, Integer.valueOf(42));
        String s = strings.get(0);
    }

    private static void unsafeAdd(List list, Object o) {
        list.add(o);
    }

🖊️이 코드는 컴파일은 되지만 실행하면 strings.get(0)의 결과를 형변환 할 때 ClassCastException을 던진다.(Integer를 String으로 변환하려 시도했기 때문에)

    private static void unsafeAdd(List<Object> list, Object o) {
        list.add(o);
    }

🖊️ unsafeAdd의 매개변수 List를 List<Object>로 바꾸면 컴파일 에러가 발생한다.

📌 비한정적 와일드카드 타입(unbounded wildcard type) 제네릭 타입을 쓰고 싶지만 실제 매개변수가 무엇인지 신경쓰고 싶지 않다면 물음표(?)를 사용하자.

    private static void unsafeAdd(List<?> list, Object o) {
        list.add(o);
    }

📍와일드카드 타입을 사용하면 null 외에 어떤 원소도 넣을 수 없다. 다른 원소를 넣으려 하면 컴파일 에러가 발생한다. 어쩄든 컴파일러는 제 역할을 한 것이다. 이러한 제약을 받아들일 수 없다면 제네릭 메서드(아이템30) 한정적 와일드 카드 타입(아이템31)을 사용하면된다.

🔸raw type을 사용하지 말라는 규칙의 소소한 예외

자바에서는 제네릭 타입 정보가 런타임(runtime) 시에 지워지는 타입 소거(type erasure)라는 개념을 사용한다. 즉, 제네릭 타입의 실제 파라미터화된 타입 정보가 런타임시에는 사라지고 컴파일러가 타입 체크를 위해 필요한 정보만을 남기는 과정이다

따라서 아래의 비한정적 와일드타입을 사용한 instanceof 연산과 raw type을 사용한 instanceof 연산은 완전히 똑같이 동작한다.


        List<String> stringList = List.of("apple", "banana", "orange");

        // 비한정적 와일드카드 타입을 사용한 instanceof 연산
        // 컴파일러 : 이 변수는 리스트 형식이지만 어떤 타입의 리스트인지는 중요하지 않구나
        // 런타임시 : 제네릭 타입 정보가 소거되므로 String이 들어오든 Integer타입이 들어오든 항상 참이된다.
        if (stringList instanceof List<?>) {
            System.out.println("This is a List.");
        }

        //비한정적 와일드카드 타입의 instanceof 연산과 완전히 동일한 결과
        if (stringList instanceof List) {
            System.out.println("This is a List.");
        }

        // raw type 을 사용한 instanceof 연산
        // 컴파일러 : 이 변수는 String의 타입의 리스트여야 하구나 (컴파일 단계에서도 체크)
        // 런타임시 : 제네릭 타입 정보가 소거되므로 String이 들어오든 Integer타입이 들어오든 항상 참이된다.
        if (stringList instanceof List<String>) {
            System.out.println("This is a List.");
        }

🍑 결론

1️⃣ raw type을 사용 하면 런타임에 예외가 발생할 수 있으니 사용하지 말아라

2️⃣ raw type은 제네릭이 도입되기 이전 코드와의 호환성을 위해 제공될 뿐이다.

3️⃣ Set는 어떤 타입의 객체도 저장할 수 있는 매개변수화 타입이고, Set<?>는 모종의 타입 객체만 저장할 수 있는 와일드카드 타입이다.

Referenced by

-

Lainlnya commented 6 months ago

인터페이슬 ?

Screenshot 2024-03-25 at 7 55 04 PM