자바에서 제네릭(generic)이란 데이터의 타입을 일반화한다는 것을 의미한다.
JDK 1.5부터 도입한 클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법이다.
이렇게 컴파일 시에 미리 타입 검사(type check)를 수행하면 다음과 같은 장점을 가진다.
- 클래스나 메서드 내부에서 사용되는 객체의 타입 안전성을 높일 수 있다.
- 반환값에 대한 타입 변환 및 타입 검사에 들어가는 노력을 줄일 수 있다.
public static void main(String[] args) {
List numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
int sum = 0;
for (Object number : numbers) {
sum += (int) number;
}
}
List에 있는 모든 숫자를 더하는 로직이다. List에 타입 지정을 안 했기 때문에 Object로 타입이 지정되고 더하는 부분에서 형변환을 직접 해줘야 한다.
위 예제에서는 형 변환을 한 번밖에 안했지만 만약 타입 지정을 안 한 List가 사용되는 곳이 1000군데가 넘는다면 1000군데서 전부 예제처럼 직접 형변환을 해줘야 하는 번거로움이 있다.
또한 만약 값을 잘못 넣어 int형이 아닌 String형의 값을 넣었다면 컴파일 에러가 발생하지 않고 런타임에 ClassCastException
이 터지게 된다.
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
int sum = 0;
for (Integer number : numbers) {
sum += number;
}
}
불필요한 형변환을 안 해도 되고 코드도 더 깔끔해진다.
타입의 안전성을 높일 수 있고 위의 제네릭을 사용하기 전과 다르게 아래와 같이 컴파일 시에 의도하지 않은 타입이 들어오는 걸 미리 막을 수 있다.
자바에서 제네릭은 클래스와 메소드에만 다음과 같은 방법으로 선언할 수 있다.
class MyArray<T> {
T element;
void setElement(T element) {
this.element = element;
}
T getElement() {
return element;
}
}
위의 예제에서 사용된 'T'를 타입 변수(type variable)라고 하며, 임의의 참조형 타입을 의미한다.
꼭 'T'뿐만 아니라 어떠한 문자를 사용해도 상관없으며, 여러 개의 타입 변수는 쉼표(,)로 구분하여 명시할 수 있다.
타입 변수는 클래스에서뿐만 아니라 메서드의 매개변수나 반환값으로도 사용할 수 있다.
위와 같이 선언된 제네릭 클래스(generic class)를 생성할 때에는 타입 변수 자리에 사용할 실제 타입을 명시해야 한다.
MyArray<Integer> myArr = new MyArray<Interger>();
위처럼 제네릭 클래스를 생성할 때 사용할 실제 타입을 명시하면, 내부적으로는 정의된 타입 변수가 명시된 실제 타입으로 변환되어 처리된다.
자바에서 타입 변수 자리에 사용할 실제 타입을 명시할 때 기본 타입을 바로 사용할 수는 없다.
이때는 위 예제의 Integer와 같이 래퍼(wrapper) 클래스를 사용해야 한다.
import java.util.*;
class LandAnimal {
public void crying() {
System.out.println("육지동물");
}
}
class Cat extends LandAnimal {
public void crying() {
System.out.println("냐옹냐옹");
}
}
class Dog extends LandAnimal {
public void crying() {
System.out.println("멍멍");
}
}
class Sparrow {
public void crying() {
System.out.println("짹짹");
}
}
class AnimalList<T> {
ArrayList<T> al = new ArrayList<T>();
void add(T animal) {
al.add(animal);
}
T get(int index) {
return al.get(index);
}
boolean remove(T animal) {
return al.remove(animal);
}
int size() {
return al.size();
}
}
public class Generic01 {
public static void main(String[] args) {
AnimalList<LandAnimal> landAnimal = new AnimalList<>(); // Java SE 7부터 생략가능함.
landAnimal.add(new LandAnimal());
landAnimal.add(new Cat());
landAnimal.add(new Dog());
// landAnimal.add(new Sparrow()); // 오류가 발생함.
for (int i = 0; i < landAnimal.size() ; i++) {
landAnimal.get(i).crying();
}
}
}
[결과값]
육지동물
냐옹냐옹
멍멍
위의 예제에서 Cat과 Dog 클래스는 LandAnimal 클래스를 상속받는 자식 클래스이므로, AnimalList<LandAnimal>에 추가할 수 있다.
하지만 Sparrow 클래스는 타입이 다르므로 추가할 수 없다.
References
:https://tecoble.techcourse.co.kr/post/2020-11-09-generics-basic/