Skip to content

Latest commit

 

History

History
173 lines (85 loc) · 7.14 KB

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

File metadata and controls

173 lines (85 loc) · 7.14 KB

로 타입은 사용하지 말자 - 아이템 26


클래스와 인터페이스 선언에 타입 매개변수(Type Paramter)가 사용되면, 이를 제네릭 클래스 혹은 제네릭 인터페이스라고 부르며 이를 통틀어 제네릭 타입이라고 한다.


각각의 제네릭 타입은 일련의 매개변수화 타입을 정의한다. 먼저 클래스의 이름이 나오고, 바로 옆에 꺾쇠괄호 안에 실제 타입 매개변수들을 나열한다.


예를 들면, List<String>은 원소의 타입이 String인 리스트를 뜻하는 매개변수화 타입이다. 여기서 String이 정규 타입 매개변수 E에 해당하는 실제 타입 매개변수이다.


그리고, 제네릭 타입을 하나 정의하면 그에 딸린 로 타입도 함께 정의된다. 이 로 타입이 바로 오늘 설명한 주제이다.



로 타입이란?


로 타입이란, 제네릭 타입에서 타입 매개변수를 전혀 사용하지 않을 때를 말한다. 코드로 설명하면 List<E>의 로 타입은 List이다.


로 타입은 타입선언에서 제네릭 타입 정보가 전부 지워진 것처럼 동작하는데, 제네릭 도입이 되기 전인 자바 1.4 버전 이하의 코드와 호환되도록 하기 위한 궁여지책이라고 할 수 있다.


제네릭 지원 전에는 컬렉션은 다음과 같이 선언했다.


스크린샷 2022-03-13 오전 11 52 21


해당 코드는 Stamp 타입만 저장을 하고 있다. 이때 루O 개발자가 실수로 Coin이라는 객체를 넣어버렸다. 과연 어떻게 될까??


image


아무 오류가 발생하지 않는다. 이대로 실행을 하고 해당 메서드를 호출하는 시점에 런타임에러(ClassCastException)가 발생하게 된다.


스크린샷 2022-03-13 오전 1 41 24


즉, 오류가 발생하고 한참 뒤인 런타임에야 해당 오류를 발견할 수 있는데, 이렇게 되면 런타임에 문제를 겪는 코드와 원인을 제공한 코드가 물리적으로 떨어져 있을 가능성이 커지게 된다.


제네릭을 활용하면 이러한 문제를 해결할 수 있게 된다.


image


이렇게 선언하면 컴파일러는 stamps에는Stamp 인스턴스만 넣어야 함을 인지하게 된다. 이제 stamps에 엉뚱한 타입의 인스턴스를 넣으면 컴파일 오류가 발생하며 무엇이 잘못되었는지를 정확하게 알려준다.


즉, 정리하자면 로 타입을 사용하는 것을 자바 언어 차원에서 막아 놓지는 않았지만 사용하지 않는 것을 권장하며 그 이유는 앞선 코드에서 보았듯이 로 타입 사용시 제네릭이 안겨주는 안정성과 표현력을 모두 잃게 되기 때문으로 정리할 수 있다.


그럼 왜 이런 로 타입이 존재하는가에 대한 의문이 생기게 되는데 바로 호환성 때문이다. 자바 1.4 이하 버전에 작성된 코드와의 마이그레이션 호환성을 지켜주기 위해서 타입 이레이저라는 방식을 사용하여 호환성을 지켜주고 있다.



로 타입을 사용하고 싶을 경우에는?


두 개의 Set에 같은 원소가 몇 개 있는지 반환하는 메서드를 만든다고 한다면, 어떤 매개변수가 들어오든 상관이 없다. 따라서 로 타입을 사용해도 괜찮지 않을까 라는 생각을 할 수 있다.


image


하지만 로 타입은 역시 안전하지 않다. 그러면 이러한 경우 어떻게 해야할까??


비한정적 와일드카드 타입을 사용하면 된다. 비한정적 와일드카드 타입은 어떤 타입이라도 담을 수 있는 가장 범용적인 매개변수화 Set 타입이다.


image


그렇다면 로 타입과 비한정적 와일드카드 타입은 무슨 차이가 있는 것일까??


앞서 말했듯이, 로 타입에는 아무 원소나 넣을 수 있으니 타입 불변식을 훼손하기가 쉽다. 반면 비한정적 와일드카드는 null을 제외한 어떤 원소도 넣을 수 없다. 즉, 다른 원소를 넣으려 하면 컴파일할 때 에러를 발생하게 된다.

만약 비한정적 와일드카드의 제약을 제거하고 싶다면 제네릭 메서드(아이템 30), 한정적 와일드카드(아이템 31)을 사용하면 된다.



그럼에도 로 타입이 사용되는 곳이 있다면??


로 타입을 쓰지 말라는 규칙에도 몇가지의 예외가 있다.


첫번째 예외는 class 리터럴에는 로 타입을 사용해야 한다.


자바 명세는 class 리터럴에 매개변수화 타입을 사용하지 못하게 했다.

여기서 class 리터널이란 String.class, Integer.class 등을 말하며 이를 하나의 객체로 생각하면 된다.

그러면 이들의 타입은, String.class의 타입은 Class<String>, Integer.class의 타입은 Class<Integer>로 볼 수 있으며 Class<T>가 로타입인 Class의 매개변수화 타입이라는 것을 우리는 확인할 수 있다.


image


위의 코드에서 보면 알 수 있듯이 자바 언어 자체에서 배열과 기본타입은 매개변수화 타입을 허용하지만 List<String>.class 타입은 지원해주지 않고 있는 것을 확인할 수 있다.

허용 : List.class, String[] class, int class
혀용하지 않음 : List<Integer>.class, List<String>.class, List<?>.class

두번째 예외는 instanceof 연산자 이다.


런타임에는 제네릭 타입 정보가 지워지므로 instanceof 연산자는 로 타입이든 비한정적 와일드카드 타입이든 완전히 동일하게 동작하게 된다.

image

즉, <?> 은 코드의 가독성을 떨어트리게 되므로 차라리 로 타입을 사용하는 것이 좋을 수 있다.



최종 결론


  • 로 타입은 제네릭이 도입되기 전 코드와의 호환성을 위해 제공될 뿐이다.

  • 로 타입은 런타임에 예외가 발생할 수 있으니 되도록 사용을 하지 말도록 하자.