[SeSACx코딩온] Java 제너릭(Generic)
1. 제너릭(Generic)이란?
제너릭(Generic)은 자바에서 데이터 타입을 일반화하여 다양한 타입을 하나의 클래스나 메서드에서 처리할 수 있도록 해주는 기능입니다. 제너릭을 사용하면 타입을 미리 지정하지 않고, 런타임이 아닌 컴파일타임에 타입 안정성을 확보할 수 있습니다.
제너릭의 필요성
- 타입 안정성: 잘못된 타입의 데이터가 사용될 때 컴파일러가 미리 에러를 감지할 수 있습니다.
- 코드 재사용성: 여러 타입을 하나의 클래스나 메서드에서 처리할 수 있으므로 코드 중복을 줄이고 재사용성을 높입니다.
- 타입 변환의 필요성 제거: 제너릭을 사용하면 타입 캐스팅을 할 필요가 없으므로 코드가 간결해집니다.
2. 제너릭 적용 클래스 비교
1) 제네릭이 없는 클래스
class CustomList {
ArrayList<String> list = new ArrayList<>();
public void addElement(String element) { list.add(element); }
public void removeElement(String element) { list.remove(element); }
public String get(int i) { return list.get(i); }
@Override
public String toString() { return "CustomList = " + list; }
}
- 이 클래스는
ArrayList<String>
를 사용하여 문자열만 처리할 수 있습니다. - 만약 다른 데이터 타입(예: 정수, 문자)을 저장하고 싶다면 새로운 클래스를 만들어야 하거나 코드를 수정해야 합니다. 이 방식은 코드 재사용성이 낮고 타입의 유연성이 떨어집니다.
2) 제네릭을 사용하는 클래스
제네릭을 사용하면 여러 타입을 처리할 수 있는 유연한 클래스를 만들 수 있습니다. 아래는 같은 기능을 제공하지만 제너릭을 사용하여 다양한 타입을 처리할 수 있도록 만든 CustomListGeneric
클래스입니다.
class CustomListGeneric<T> {
ArrayList<T> list = new ArrayList<>();
public void addElement(T element) { list.add(element); }
public void removeElement(T element) { list.remove(element); }
public T get(int i) { return list.get(i); }
@Override
public String toString() { return "CustomListGeneric = " + list; }
}
CustomListGeneric
클래스는 제너릭 타입 파라미터T
를 사용합니다.- 정수(Integer), 문자(Character), 문자열(String) 등 어떤 타입이든 처리할 수 있습니다. 제네릭 덕분에 코드 중복을 줄일 수 있고, 여러 타입을 유연하게 다룰 수 있습니다.
3. 제네릭 클래스 활용
1) 제네릭을 사용하지 않은 경우
CustomList li = new CustomList();
li.addElement("first");
li.addElement("second");
li.addElement("third");
System.out.println(li.toString()); // 출력: CustomList = [first, second, third]
li.removeElement("second");
System.out.println(li.toString()); // 출력: CustomList = [first, third]
System.out.println(li.get(1)); // 출력: third
문자열만 처리할 수 있습니다. 만약 다른 타입의 데이터를 추가하고 싶다면 코드 전체를 수정합니다.
2) 제네릭을 사용하는 경우 (타입 안전성 확보 예시)
(1) 정수형(Integer) 데이터를 처리하는 경우
CustomListGeneric<Integer> gli = new CustomListGeneric<>();
gli.addElement(3);
gli.addElement(6);
gli.addElement(7);
System.out.println(gli.toString()); // 출력: CustomListGeneric = [3, 6, 7]
gli.removeElement(6);
System.out.println(gli.toString()); // 출력: CustomListGeneric = [3, 7]
System.out.println(gli.get(1)); // 출력: 7
- 제네릭을 사용하면 하나의 클래스로 다양한 타입의 데이터를 처리할 수 있습니다.
- 이 코드에서는 정수형 데이터를 처리합니다. 제네릭 덕분에 Integer 타입으로 리스트를 선언하고, 정수형 데이터를 저장할 수 있습니다. (당연히 타입도 숫자)
(2) 문자형(Character) 데이터를 처리하는 경우
CustomListGeneric<Character> gli2 = new CustomListGeneric<>();
gli2.addElement('A');
gli2.addElement('C');
gli2.addElement('R');
System.out.println(gli2.toString()); // 출력: CustomListGeneric = [A, C, R]
gli2.removeElement('A');
System.out.println(gli2.toString()); // 출력: CustomListGeneric = [C, R]
System.out.println(gli2.get(0)); // 출력: C
- 여기서는 문자형 데이터를 처리합니다.
- 동일한
CustomListGeneric
클래스가 Character 타입으로 선언되어 문자를 저장하고 처리할 수 있습니다. CustomListGeneric
가class CustomListGeneric<T>
로 되어있기 때문에 동일한 클래스라도 타입을 다르게 저장할 수 있습니다.
4. 제네릭의 이점
1) 타입 안전성 확보
제네릭을 사용하면 컴파일 시점에 타입을 체크하므로, 잘못된 타입을 다루는 오류를 미리 방지할 수 있습니다. 예를 들어, CustomListGeneric<Integer>
로 선언된 리스트에는 Integer 타입만 저장할 수 있으므로 다른 타입을 잘못 추가하는 실수를 막을 수 있습니다.
2) 코드 중복 감소
제네릭을 사용하면 여러 타입을 하나의 코드로 처리할 수 있으므로, 타입마다 새로운 클래스를 만들 필요가 없습니다. 이를 통해 코드의 중복을 줄일 수 있고, 유지보수가 쉬워집니다.
3) 타입 변환의 필요성 제거
제네릭을 사용하면 데이터를 가져올 때 타입 캐스팅을 할 필요가 없습니다. 예를 들어, CustomListGeneric<Integer>
에서 값을 가져올 때 타입 캐스팅 없이 Integer 값을 바로 사용할 수 있습니다.
예를들어 제너릭을 사용하지 않을 경우,
ArrayList list = new ArrayList();
list.add(3); // int 값을 추가
Integer num = (Integer) list.get(0); // 명시적 타입 캐스팅 필요
list.get(0)이 Object 타입을 반환하기 때문에, 명시적으로 Integer로 타입 캐스팅
을 해야 합니다. 만약 잘못된 타입으로 캐스팅을 시도하면 런타임 오류가 발생할 수 있습니다.