Don't use raw type in Java Generic

jiho·2021년 5월 27일
0

EffectiveJava

목록 보기
9/12

Effective Java Item 26 Raw type은 사용하지말라에 대한 내용 정리입니다.

Raw Type 이란?

우선 제네릭 클래스 혹은 제네릭 인터페이스에 대한 정의부터하겠습니다. 클래스와 인터페이스 선언에 타입 매개변수(type parameter)가 쓰이면, 이를 제네릭 클래스 혹은 제네릭 인터페이스라 합니다. 통틀어 제네릭 타입이라고 합니다.

제네릭 타입을 정의하면 그에 딸린 Raw Type도 함께 정의됩니다. 제네릭타입에서 타입 매개변수를 전혀 사용하지 않을 때를 말합니다. List<E>의 Raw Type은 List입니다. 제네릭이 도래하기 전 코드와 호환되도록 하기 위해 여전히 사용할 수 있습니다.

private final Collection stamps = ...;

하지만 위 코드를 사용하면 Stamp대신 Coin을 넣어도 아무 오류없이 컴파일되고 실행됩니다.(Generic의 장점을 활용할 수 없습니다.)

for (Iterator i = stamps.iterator(); i.hasNext();) {
	Stamp stamp = (Stamp) i.next();
    stamp.cancel();
}

위 코드의 형변환이 발생하기 전까지 오류를 알아채지 못합니다. 오류는 가능한 한 발생 즉시, 이상적으로는 컴파일할 때 발견하는 것이 좋습니다. 위 처럼 Raw type을 사용하면 런타임에야 오류를 알아챌 수 있게됩니다.

그리고 ClassCastException이 발생하면 Stamps에 동전을 넣은 지점을 찾기 위해 코드 전체를 훑어 봐야할 수 도 있습니다.

하지만 제네릭을 활용하면 타입 선언 자체에서 타입 안정성을 확보할 수 있다.

private final Collection<Stamp> stamps = ...;

이제 Coin객체를 stamps에 넣는 코드가 있으면 컴파일 시간에 오류를 인지할 수 있습니다.

정리하자면 Raw Type을 사용하는 것은 제네릭이 주는 안정성과 표현력을 모두 잃게 됩니다. 호환성 때문에 아직 존재하긴 하지만 사용해서는 안됩니다.

Raw Type인 List보다는 List<Object>을 사용하자.

List같은 raw type은 사용해서는 안되지만 List<Object>와 같이 모든 클래스의 조상인 Object를 매개변수화 타입으로 사용하는 경우는 괜찮습니다. 차이는 List<Object>는 모든 타입을 허용한다는 의사를 컴파일러에 명확히 전달합니다.

Raw Type 인 List를 매개변수로 받는 메소드에 List<String>을 넘길 수 있지만 List<Object>를 메개변수로 받는 메소드는 넘길 수 없습니다. 제네릭 하위 타입 규칙에 의해 그렇습니다. List<Stirng>은 Raw Type의 하위 타입이지만, List<Object>의 하위 타입은 아닙니다. 결과적으로 List<Object>같은 매개변수화 타입을 사용할 때와 달리 List같은 raw type을 사용하면 타입 안정성을 잃게 됩니다.

원소를 몰라도 될 때는 Raw Type말고 Wildcard 를 사용하자.

원소의 타입을 몰라도 되는 Raw Type을 쓰고 싶을 때도 있습니다.

2개의 집합(Set)을 받아서 공통 원소를 반환하는 메서드를 작성한다고 해봅시다.
아래는 generic을 처음 사용하는 사람이 작성할 법한 코드입니다.

static int numElementsInCommon(Set s1, Set s2) {
	int result = 0;
    for (Object o1: s1) {
    	if(s2.contains(o1))
        	result++;
    }
    return result;
}

이 메서드는 동작은 하지만 Raw Type을 사용했기 때문에 안전하지 않습니다. 코드를 개선하려면 비한정적 와일드카드 타입을 대신사용하는 것이 좋습니다.

static int numElementsInCommon(Set<?> s1, Set<?> s2) { ... }

위 메서드는 어떤 타입이라도 담을 수 있는 가장 범용적인 매개변수화 Set 타입입니다.

Set<?>Set 타입의 차이는 간단히 말하자면 Raw Type은 아무 타입이나 넣을 수 있기 때문에 타입 불변식을 훼손하기 쉽습니다. 반면에 Set<?>에는 어떠한 원소도 넣을 수 없습니다.(Null을 제외) 물론 꺼낼 때도 객체의 타입을 전혀 알 수 없습니다. 이러한 제약을 없애고 싶으면 한정적 와일드카드 타입을 사용하면 됩니다.

Raw Type은 정말 쓰면 안될까?

개인적으로 협업을 하는 상황이라면 일관성있게 안쓰는게 좋겠지만 Effective Java에서 유용한 사례가 있긴하다.

Class literal에는 Raw Type을 써야한다.

A class literal is an expression consisting of the name of a class, interface, array, or primitive type followed by a . and the token class. The type of a class literal is Class. It evaluates to the Class object for the named type (or for void) as defined by the defining class loader of the class of the current instance.

클래스 리터럴은 class, interface, array, primitive type의 이름을 나타내는 표현입니다. class literal의 타입은 Class입니다.

class 리터럴에는 매개변수화 타입을 사용하지 못하게했습니다.

List.class, String[].class

Class Literal은 따로 정리하도록하겠습니다.

instanceof 와 함께 쓰일때

런타임에는 제네릭 타입 정보가 지워지므로 instanceof 연산자는 비한정적 와일드타입 이외의 매개변수화 타입에는 적용할 수 없다.

그리고 Raw Type이든 비한정 와일드카드 타입이든 instanceof는 완전히 똑같이 동작합니다. 그래서 instanceof 연산자를 사용할 때는 raw type사용하는 것이 더 깔끔합니다.

정리

Raw Type이라는 개념을 알 수 있었고 좋은 팁을 하나 얻었습니다. 무심결에 Raw Type을 남용할 수도 있었는데 언제써야되고 왜 써야하는지를 알 수 있는 의미있는 정리였습니다.

  • Raw Type은 과거 코드들과의 호환성을 위해 존재 하고 Generic Type을 사용하도록하자
  • Generic 타입 안정성을 위해 사용한다.
  • 컴파일 시간에 Generic Type정보는 지워진다.
  • 되도록이면 에러는 컴파일에서 잡아내는 것이 옳다.
  • Parameter로 원소의 타입을 하나에 한정짓기 싫다면 혹은 범용적인 매개변수화를 원한다면 Wildcard Type(?)를 활용하자
  • wildcard를 견고하게 만들고 싶다면 bounded wildcard(<? extends ParentClass>와 같은)를 사용하는 것이 좋다.
profile
Scratch, Under the hood, Initial version analysis

0개의 댓글