제네릭을 사용하면 코드 재사용과 타입 안전성을 확보할 수 있다.
public class Box<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
<>를 사용한 클래스를 제네릭 클래스라고 한다.
제네릭 클래스를 사용할 땐 타입을 미리 결정하지 않는다.
<T> 의 T를 타입 매개변수라고 하고, 이후에 Integer, String 같은 타입으로 변한다.
클래스 내부에 T 타입이 필요한 곳에 T value와 같이 타입 매개변수를 적어두면 된다.
Box<Integer> integerBox = new Box<>(); // Integer로 생성
integerBox.setValue(10);
Integer integer = integerBox.getValue();
Box<String> stringBox = new Box<>(); // String으로 생성
stringBox.setValue("hello");
String string = stringBox.getValue();
System.out.println(integer); //10
System.out.println(string); // "hello"
제네릭의 핵심은 사용할 타입을 미리 결정하지 않는다는 점이다.
실제 생성 시점에 타입을 결정하는 것이다.
용어 정리
제네릭이라는 단어는 일반적인, 범용적인이라는 영어 단어 뜻이다.
풀어보면 특정 타입에 속한 것이 아니라 일반적으로, 범용적으로 사용할 수 있다는 뜻이다.
클래스나 인터페이스를 정의할 때 타입 매개변수를 사용하는 것을 말한다.
제네릭 클래스, 제네릭 인터페이스를 모두 합쳐서 제네릭 타입이라 한다.
타입은 클래스, 인터페이스, 기본형(int 등)을 모두 합쳐서 부르는 말이다.
예: class Box<T> { private T t; }
여기에서 Box<T> 를 제네릭 타입이라 한다.
제네릭 타입을 사용할 때 제공되는 실제 타입이다.
예: Box<Integer>
여기에서 Integer 를 타입 인자라 한다.
제네릭 명명 관례
타입 매개변수는 일반적인 변수명처럼 소문자로 사용해도 문제는 없다. 하지만 일반적으로 대문자를 사용하고 용도에 맞는 단어의 첫글자를 사용하는 관례를 따른다.
class KeyValue<K, V> {}
타입 매개 변수를 특정 타입으로 제한할 수 있다.
public class ColorPlay<T extends Color> {...}
<T extends Color> 을 통해 타입 매개변수 T를 Color과 그 자식들만 받을 수 있도록 제한한다.
자바 컴파일러는 T의 상한이 Color이라고 범위를 예측할 수 있다.
제네릭에 타입 매개변수 상한을 사용해 타입 안전성을 지키면서 상위 타입의 원하는 기능까지 사용할 수 있다.
public class Box {
public static <T> T genericMethod(T t) {
return t;
}
}
// main
Integer integer = Box.<Integer>genericMethod(10);
제네릭 메서드는 메서드를 호출할 때 타입 인자를 전달해서 타입을 지정하는 것이다.
Box.<Integer>genericMethod(10); 처럼 타입을 지정하면서 메서드를 호출한다.
제네릭 메서드도 제네릭 타입과 같이 타입 매개변수를 제한할 수 있다.
public static <T extends Number> T numberMethod(T t) {}
Integer integer = Box.genericMethod(10);
Integer integer = Box.genericMethod(10.1); //error
제네릭 메서드 호출마다 타입 인자를 계속 전달하는 것은 매우 비효율이다.
자바 컴파일러는 호출 시 인자를 통해 제네릭 메서드의 타입을 추론한다.
와일드 카드는 제네릭 타입을 더 편리하게 사용할수 있도록 도와준다.
public class WildCard {
static void printWildcardV1(Box<?> box) {
System.out.println("? = " + box.getValue());
}
static void printWildcardV2(Box<? extends Animal> box) {
Animal animal = box.getValue();
System.out.println("이름 = " + animal.getName());
}
static Animal printAndReturnWildcard(Box<? extends Animal> box) {
Animal animal = box.getValue();
System.out.println("이름 = " + animal.getName());
return animal;
}
}
와일드 카드는 ? 를 사용해서 정의한다.
와일드 카드는 이미 만들어진 제네릭 타입을 활용할 때 사용한다.
와일드 카드는 일반 메서드에 사용할 수 있고 단순히 매개변수로 제네릭 타입을 받을 수 있다.
static void printWildcardV2(Box<? extends Animal> box) {
Animal animal = box.getValue();
System.out.println("이름 = " + animal.getName());
}
제네릭 메서드와 마찬가지로 와일드카드에도 상한 제한을 둘 수 있다.
타입 매개변수가 꼭 필요한 경우
와일드카드는 제네릭을 정의할 때 사용하는 것이 아니다.
만약 제네릭 타입으로 반환하고 싶다면 제네릭 메서드를 사용해야 하며, 와일드카드를 사용하면 전달한 타입을 명확하게 반환할 수 없다.
이레이저는 지우개라는 뜻이다.
제네릭은 자바 컴파일 단계에서만 사용되고, 컴파일 이후에는 제네릭 정보가 삭제된다.
제네릭에 사용한 타입 매개변수가 모두 사라지는 것이다.
class EraserBox<T> {
public boolean instanceCheck(T param) {
return param instanceof T;
}
public void create() {
return new T();
}
}
여기서 T 는 런타임에 모두 Object가 되어버린다.
이렇게 되면 instananceof 는 항상 참이 되는 문제가 발생한다.
new T() 도 항상 new Object가 되어 버린다.
따라서 자바는 타입 매개변수에 instanceof new 를 허용하지 않는다.