제네릭 클래스 또는 제네릭 인터페이스란? : 클래스와 인터페이스 선언에 타입 매개변수가 쓰인 것
예를 들면 List<E>
와 같은 형태가 제네릭 인터페이스가 되겠다.
여기서 꺽쇠(<>
)안에 정의하는 타입이 매개변수화 타입(parameterized type)이라고 한다.
Raw 타입이란 제네릭 타입에서 타입 매개변수를 전혀 사용하지 않을 때를 말한다. 예를 들면 List<E>
에서 List가 Raw 타입이 되는 것이다. 그렇다면 왜 Raw 타입을 사용하지 말라고 하는 것일까? 다음 예제를 통해 살펴보자
// Stamp 인스턴스만 취급
private final Collection stamps = ...;
이 코드를 사용하면 실수로 Stamp
대신 Coin
따위를 넣어도 아무 오류 없이 컴파일에 성공하고 실행된다.(경고 표시 정도는 컴파일러가 해줄 것이다). 그리고 아마 이 컬렉션에서 요소를 꺼내 올때 Stamp
로 받아오려고 한다면 실제 타입은 Coin
이기 때문에 ClassCastException
이 발생할 것이다.
이럴 경우 아래와 같이 제네릭 타입으로 선언해주면 컴파일 시점에 오류를 잡아낼 수 있다.
private final Collection<Stamp> stamps = ...;
즉, 이제부터는 stamps
객체에는 Stamp
의 인스턴스만 넣어야 함을 컴파일러가 인지하게 된 것이다.
이렇듯 Raw 타입을 쓰는 것을 언어 차원에서 막아 놓지는 않았지만 절대로 사용해서는 안된다. 왜냐하면 Raw 타입을 사용하면 제네릭이 안겨주는 안전성과 표현력을 모두 잃게 되기 때문이다.
List
와 같이 Raw 타입으로 사용해서는 안되지만, List<Object>
와 같이 임의 객체를 허용하는 매개변수 타입을 정의하는 것은 괜찮다. 그렇다면 이 둘의 차이는 무엇일까?
List
와 List<Object>
의 차이
컴파일러에게 제네릭 타입 사용 여부를 선언 하였는지의 여부의 차이가 있다
List
에는 List<String>
을 넘길 수 있지만, List<Object>
에는 넘길 수 없다
String
이 Object
의 하위 타입이 아니기 때문이다.List
와 같이 사용하면 타입 안전성을 잃게 된다.
만약 비한정적으로 타입을 선언하고 싶은 경우에는 와일드카드 타입(unbounded wildcard type)을 사용하는 것이 좋다. 아래와 같이 사용해볼 수 있다
static int numElementsInCommon(Set<?> s1, Set<?> s2) {...}
그렇다면 와일드카드가 주는 장점은 무엇일까? 간단하게 말하자면 와일드카드 타입은 안전하고, Raw 타입은 안전하지 않다. Collection<?>
에는 null 이외에 다른 원소를 넣을 수 없다. 만약 다른 원소를 넣으려 한다면 컴파일 시점에 오류를 발생시킨다.
Set<Object>
와 Set<?>
는 안전하지만 Set
은 안전하지 않다.