데이터 타입을 일반화하여 클래스나 메서드, 프로퍼티를 정의할 때 데이터 타입을 변수로 지정하고, 사용할 때 그 타입을 정해주는 것 이다.
이 때 지정한 데이터 타입을 타입 파라미터, 사용할 때 구체적으로 명시해주는 타입을 타입 아규먼트라고 한다.
제네릭이 왜 필요할까?
1-1. 컴파일에서 타입 검사를 해서 실수 방지
여러 타입을 받는 함수를 만들고 싶다고 가정하자.
제네릭이 없던 시절, 여터 타입에 적용하기 위해서는 객체 최고 조상인 Object로 타입을 지정하고 다운캐스팅을 하였다.
이 방식은 개발자가 코드를 잘못 짜서 다른 타입으로 캐스팅한 경우, 컴파일 시점에 에러가 발생하지 않는다.
class Dog {}
class Cat {}
class Animal {
private Object[] animal;
public Animal(Object[] animal) {
this.animal = animal;
}
public Object getAnimal(int index) {
return animal[index];
}
}
public static void main(String[] args) {
Dog[] arr = {
new Dog(),
new Dog()
};
Animal animal = new Animal(arr);
Dog d = (Dog) animal.getAnimal(0);
Cat c = (Cat) animal.getAnimal(0); //에러
}
제네릭을 사용하면, 이러한 실수를 방지할 수 있다.
class Dog {}
class Cat {}
class Animal {
private Object[] animal;
public Animal(T[] animal) { //T는 타입 파라미터
this.animal = animal;
}
public T getAnimal(int index) {
return animal[index];
}
}
public static void main(String[] args) {
Dog[] arr = {
new Dog(),
new Dog()
};
Animal<Dog> dogs = new Animal<>(arr); //Dog은 타입 아규먼트
Dog d = (Dog) animal.getAnimal(0);
Cat c = (Cat) animal.getAnimal(0); //에러
}
1-2. 불필요한 캐스팅을 없애 성능 향상
Object로 받아서 다운캐스팅을 했었는데, 이것은 추가적인 오버헤드가 발생한 것이다.