JAVA/Effective Java

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

Garonguri 2022. 5. 10. 10:07
728x90

  

Generic이란?

- 자바에서 제네릭이란 데이터의 타입(data type)을 일반화한다(generalize)는 것을 의미.

- 제네릭은 클래스나 메소드에서 사용할 내부 데이터 타입을 컴파일 시에 미리 지정하는 방법.

 

 

Generic Class/Interface란?

- 클래스나 인터페이스 선언에 타입 매개변수가 쓰인 클래스나 인터페이스.

- 한마디로 Generic Type이라 한다.

 

각각의 제네릭 타입은 일련의 매개변수화 타입을 정의한다.

기본형 : class/Interface_name<parameter_type>

ex) List<E> Interface

- List<String>

- 원소의 타입이 String인 리스트를 뜻하는 매개변수화 타입.

- String : 정규화 타입 매개변수 <E>에 해당하는 실제 매개변수


Row Type?

각각의 제네릭 타입을 정의하면 그에 따른 로 타입 매개변수도 함께 정의된다.

Row type : 제레닉 타입에서 타입 매개변수를 전혀 사용하지 않을 때 타입을 의미.

ex) List<E> Interface

- Row type : List

- 타입 선언에서 제네릭 타입 정보가 전부 지워진 것 처럼 동작.

  • Raw type- 런타임에 와서야 오류가 발생하고 있음을 알아차릴 수 있다. 
  • private final Collection stamps = ...;
    stamps.add(new Coin(...)) 
    // 실수로 도장 대신 Coin 인스턴스를 add() 한다 해도 오류 없이 컴파일된다.
    // "unchecked call" 경고를 내뱉기는 함.
    
    해당 동전을 꺼낼 때야 ClassCastException을 던진다.
  • Generic
    private final Collection<Stamp> stamps = ...;
    //stamps에는 Stamp의 인스턴스만 넣어야 되는 것을 컴파일러가 인지할 수 있다.
    stamps.add(new Coin(...))
    
    따라서 컴파일 시 오류가 발생한다.
  • Raw Type을 사용한 경우 Type이 맞지 않는 인스턴스에 대한 에러를 런타임에 확인할 수 있는 치명적인 단점이 존재한다.
  • Raw Type을 사용하면 Generic이 안겨주는 안정성과 표현력을 모두 잃을 수 있다.
  • Generic 을 사용한다면 컴파일 타임에 Type을 체크하여 런타임 오류를 예방할 수 있다.
    • 컬렉션이 파라미터화 선언이 되어있더라도, 해당 컬렉션을 인자로 받는 메서드의 파라미터가 로 타입이라면 런타임 오류가 난다.

 

결론 : Row type은 그냥 쓰지 말아라.

 

Row type을 만들어 놓은 이유?

- 호환성 때문.

- 자바가 제네릭을 받아들이기 까지 거의 10년의 시간이 걸렸기 때문에, 기존 코드를 모두 수용하며 제네릭을 사용하는 코드와도 호환되도록 해야했기 때문.

 


임의 객체를 허용하는 매개변수화 타입 _<Object>

- List와 달리 List<Object>와 같이 임의 객체를 허용하는 매개변수화 타입은 사용해도 괜찮다.

- List<Object>는, 모든 타입을 허용한다는 의사를 컴파일러에 명확히 전달한 것. 그에반해 List는 제네릭 타입이 아니다.


List를 매개변수로 받는 메서드에는 List<String>을 넘길 수 있다. 그러나 List<Object>를 매개변수로 받는 메서드에는 List<String>을 넘길 수 없다. 

제네릭의 하위 타입 규칙 때문.
List<String>은 로 타입인 List의 하위 타입.
List<String>은 List<Object>의 하위 타입이 아니다.

따라서 List<Object>(매개변수화 타입)이 아닌 List(Row 타입)을 사용하면 타입 안정성을 잃게 된다.

오류는 가능한 한 발생 즉시 발견하는 것이 좋다.

Ex) Low type을 사용한 경우

public class item26 {
    public static void main(String[] args) {
        List<String> strings = new ArrayList<>();
        unsafeAdd(strings, Integer.valueOf(42));
        String s = strings.get(0); //ClassCastException
        System.out.println(s);
        
        컴파일은 되지만, 런타임에서 예외를 던진다.
        //Exception in thread "main" java.lang.ClassCastException:
        // class java.lang.Integer cannot be cast to class java.lang.String
        // (java.lang.Integer and java.lang.String are in module java.base of loader 'bootstrap')
        // at item26.main(item26.java:9)
    }

	//low type을 사용한 메서드
    private static void unsafeAdd(List list, Object o){
        list.add(o);
    }
}

Ex) 매개변수화 type을 사용한 경우

public class item26 {
    public static void main(String[] args) {
        List<String> strings = new ArrayList<>();
        unsafeAdd(strings, Integer.valueOf(42));
        
        컴파일조차 되지 않는다.
        /*
        Required type: List<Object>
        Provided: List<String>
         */
        String s = strings.get(0);
        System.out.println(s);

    }

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

오류는 가능한 한 빠르게 즉시 발견하는 것이 좋다.

 


잠깐..... 그럼 원소의 타입을 모를 때, Row type을 쓰면 되지 않나요?

아니 ! 기본적으로 row type을 사용하는 것은 안정하지 않다. 아무 원소나 넣을 수 있으므로 타입 불변식을 훼손하기 쉽다.
- 그럴 때 "비한정적 와일드카드 타입"을 대신 사용하자.
- 비한정적 와일드카드 타입은 어떤 타입이라도 담을 수 있는 가장 범용적인 매개변수화 type이다.
- 모종의 타입 객체만 저장할 수 있기 때문에 타입 안전하며, 유연하다.

ex) 제네릭 타입 Set<E>의 비한정적 와일드카드 타입은 Set<?>
ex) 비한정적 와일드 카드 Collection<?>는 null 외에는 어떠한 원소도 넣을 수 없다. 또한 컬렉션에서 꺼낼 수 있는 객체 타입도 전혀 알 수 없게 했다. 따라서 안전하다.

제네릭 타입을 쓰고 싶지만 실제 타입 매개변수가 무엇인지 신경 쓰고 싶지 않다면 비한정적 와일드카드 타입 ?를 사용해라.

예외

1. class 리터럴에는 raw type을 써야한다.

- 자바에서는 class 리터럴에 매개변수화 타입을 사용할 수 없게 했다. (배열, 기본 타입은 허용)

- List.class, String[].class, int.class (허용) List<String>.class, List<?>.class (불허)

 

2. instanceof 연산자는 비한정적 와일드카드 타입 이외의 매개변수화 타입에는 적용할 수 없다.

- 런타임에는 제네릭 타입 정보가 지워지기 때문이다.

- raw type과 비한정적 와일드카드 타입은 instanceof에서는 똑같이 동작한다. 따라서 깔끔하게 사용하기 위해 로 타입을 사용한다.

-> instanceof로 타입을 확인한 다음 와일드카드 타입으로 형변환한다.

 

Set으로 보는 전체 정리

Set<Object>는 어떤 타입의 객체도 저장할 수 있는 매개변수화 타입이다.
Set<?>는 모종의 타입 객체만 저장할 수 있는 와일드카드 타입이다.
로 타입인 Set은 제네릭 타입 시스템에 속하지 않는다.
Set<Object>와 Set<?>는 안전하지만, 로 타입인 Set은 안전하지 않다.

 

728x90