제네릭 타입은 타입으로 매개변수화된 제네릭 클래스 또는 인터페이스예요. 이를 통해 컴파일러가 클래스나 인터페이스의 타입 안전성을 강제할 수 있어요.
제네릭이 아닌 다음 Container 인터페이스를 살펴봐요:
class Container {
private Object object;
public void set(Object object) { this.object = object; }
public Object get() { return object; }
}
Object 타입을 받고 반환하기 때문에 모든 기본형이 아닌 타입과 함께 동작해요.
하지만 대가가 따르는데, 어떤 코드는 Integer를 기대하며 get을 호출하고 다른 코드는 String을 넣으며 set을 호출하면 런타임 예외가 발생할 수 있어요.
제네릭 클래스와 제네릭 인터페이스는 다음과 같은 형식을 가져요:
class ClassName<T1, T2, ..., Tn> { ... }
interface InterfaceName<T1, T2, ..., Tn> { ... }
클래스나 인터페이스 이름 뒤에 오는, 꺾쇠괄호(<>)로 둘러싸인 타입 매개변수 부분은 타입 매개변수(타입 변수라고도 해요) T1, T2, ..., Tn을 지정해요.
이들은 클래스나 인터페이스의 본문 안에서 사용해서 제네릭 타입 안전성을 얻을 수 있어요.
다음은 Container의 제네릭 버전이에요:
class Container<E> {
private E object;
public void set(E object) { this.object = object; }
public E get() { return object; }
}
이제 생성할 때 어떤 타입을 담을지 선언해야 하고, 컴파일러가 그 제약을 강제해요:
// empty <> can infer from context
Container<String> stringContainer = new Container<>();
// compiler knows this is a String, so it is allowed
stringContainer.set("Some string");
// no cast needed, compiler knows it is a String
String result = stringContainer.get();
// this causes a compiler error:
stringContainer.set(42);