09. Generics
1. Generics 란?
제네릭스(Generics)는 컴파일 시 타입을 체크하고, 다양한 데이터 타입을 처리할 수 있도록 도와주는 기능
- 와일드카드(Generics Wildcard) : 유연한 타입을 허용
- 기본형 와일드 카드 : List<?>
=> List<?> → "어떤 타입의 리스트인지 모르겠지만 받아들일 수 있음."
- 상한제한형 와일드 카드 : List<? extends T> T와 T의 하위클래스 허용
=> List<? extends Number> → Number 및 Integer, Double 등 가능
- 하한제한형 와일드 카드 : List<? super T> T와 T의 상위클래스 허용
=> List<? super Integer> → Integer, Number, Object 가능
😂😂😂
와일드 카드 사용예시)
---------------------------------------------------------------------------------------------
✅ ? → 타입이 정확히 필요하지 않고 모든 리스트를 받아야 할 때
✅ ? extends T → 읽기 전용, 리스트에서 값을 가져올 때 사용
✅ ? super T → 쓰기 전용, 리스트에 값을 추가할 때 사용
2. 제네릭스 활용 예제 및 사용 이유
1) 제네릭스를 사용하지 않은 클래스 경우 문제점
class Box {
private Object value;
public void setValue(Object value) {
this.value = value;
}
public Object getValue() {
return value;
}
}
public class Main {
public static void main(String[] args) {
Box intBox = new Box();
intBox.setValue(10);
Box strBox = new Box();
strBox.setValue("Hello");
// 형변환 필요 (타입 안정성 문제 발생 가능)
int num = (Integer) intBox.getValue(); // ✅ 가능
String text = (String) strBox.getValue(); // ✅ 가능
}
}
🚨 문제점: Object 타입이라 형변환이 필요하고, 잘못된 형변환 시 런타임 오류 발생 가능!
2) 제네릭스를 사용한 클래스 경우
class Box<T> { // "T"는 타입 파라미터 (임의의 타입을 지정 가능)
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
public class GenericExample {
public static void main(String[] args) {
Box<Integer> intBox = new Box<>(); // 정수형 박스
intBox.setValue(10);
int num = intBox.getValue(); // 형변환 필요 없음! ✅
Box<String> strBox = new Box<>(); // 문자열 박스
strBox.setValue("Hello");
String text = strBox.getValue(); // 형변환 필요 없음! ✅
System.out.println(num); // 10
System.out.println(text); // Hello
}
}
✅ 타입을 유동적으로 변경하여 사용할 수 있다.
📌 제네릭 사용 이유!
✅ 제네릭은 타입 안전성을 높이고, 코드 중복을 줄이는 강력한 기능!
✅ 제네릭 클래스를 사용하면 타입 캐스팅이 필요 없음.
✅ 와일드카드(?)를 사용하면 제네릭을 더 유연하게 활용 가능!
3. 제네릭 메서드/인터페이스
1) 제네릭 메서드
class Util {
// 제네릭 메서드 정의
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.print(element + " ");
}
System.out.println();
}
}
public class GenericMethodExample {
public static void main(String[] args) {
Integer[] intArray = {1, 2, 3};
String[] strArray = {"Hello", "World"};
Util.printArray(intArray); // ✅ 가능 (출력: 1 2 3)
Util.printArray(strArray); // ✅ 가능 (출력: Hello World)
}
}
✅ 메서드에 T를 추가하여 다양한 타입을 받을 수 있도록 설정!
✅ 코드를 재사용할 수 있어 효율적!
2) 제네릭 인터페이스
// 제네릭 인터페이스 정의
interface Calculator<T> {
T add(T a, T b);
}
// Integer 타입 계산기
class IntegerCalculator implements Calculator<Integer> {
public Integer add(Integer a, Integer b) {
return a + b;
}
}
// Double 타입 계산기
class DoubleCalculator implements Calculator<Double> {
public Double add(Double a, Double b) {
return a + b;
}
}
public class GenericInterfaceExample {
public static void main(String[] args) {
Calculator<Integer> intCalc = new IntegerCalculator();
System.out.println(intCalc.add(5, 10)); // 15
Calculator<Double> doubleCalc = new DoubleCalculator();
System.out.println(doubleCalc.add(5.5, 2.2)); // 7.7
}
}
✅ Calculator<T>를 사용해 다양한 타입의 계산기를 만들 수 있음!
🎯 제네릭 정리
제네릭 클래스 | 다양한 타입을 지원하는 클래스 | class Box<T> { T value; } |
제네릭 메서드 | 여러 타입을 지원하는 메서드 | <T> void printArray(T[] array) |
제네릭 인터페이스 | 타입에 따라 구현이 달라지는 인터페이스 | interface Calculator<T> { T add(T a, T b); } |
와일드카드 (?) | 불특정한 타입을 표현 | List<?> list |
? extends T | T의 하위 타입만 허용 (읽기 전용) | List<? extends Number> |
? super T | T의 상위 타입만 허용 (쓰기 가능) | List<? super Integer> |