학습 키워드
- Generic Class & Interface
- Type Parameter
- Type Safety
- Code Reusability
- Bounded Type Parameter
- Multiple Type Parameters
- Raw Type
- Type Inference
학습 내용
1. 제네릭 클래스의 기본 구조와 필요성
Java에서 제네릭이 없던 시절의 코드를 보면 다음과 같은 문제점이 있었다.
// 제네릭 이전의 코드
class OldBox {
private Object item;
public void setItem(Object item) {
this.item = item;
}
public Object getItem() {
return item;
}
}
// 사용 예시
OldBox box = new OldBox();
box.setItem("Hello"); // String -> Object (자동 형변환)
String str = (String) box.getItem(); // Object -> String (명시적 형변환 필요)
Integer num = (Integer) box.getItem(); // 런타임 에러! ClassCastException
위 코드의 문제점:
- 모든 데이터를 Object로 다루어야 함
- 타입 캐스팅이 필수적
- 잘못된 타입 캐스팅 시 런타임 에러 발생
- 컴파일 시점에서 타입 안정성 보장 불가
2. 제네릭을 통한 타입 안정성 확보
제네릭을 사용하면 컴파일 시점에서 타입을 체크할 수 있다.
class Box<T> {
private T item;
public void setItem(T item) {
this.item = item;
}
public T getItem() {
return item;
}
}
// 타입 안정성이 보장된 사용 예시
Box<String> stringBox = new Box<>();
stringBox.setItem("Hello"); // 컴파일 OK
stringBox.setItem(100); // 컴파일 에러! - 타입 불일치
String str = stringBox.getItem(); // 캐스팅 불필요
3. 코드 재사용성 향상을 위한 제네릭 활용
하나의 제네릭 클래스로 다양한 타입을 처리할 수 있다.
// 다양한 타입의 페어를 처리하는 제네릭 클래스
class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
public void setKey(K key) { this.key = key; }
public void setValue(V value) { this.value = value; }
}
// 다양한 타입 조합으로 재사용
Pair<String, Integer> studentScore = new Pair<>("John", 95);
Pair<Integer, String> errorCode = new Pair<>(404, "Not Found");
Pair<Double, Double> coordinate = new Pair<>(37.5665, 126.9780);
4. 제네릭의 고급 활용
4.1 한정된 타입 매개변수 (Bounded Type Parameter)
특정 타입의 하위 타입으로만 제한할 수 있다.
class NumberBox<T extends Number> {
private T number;
public void set(T number) {
this.number = number;
}
public double getDoubleValue() {
return number.doubleValue(); // Number 클래스의 메서드 사용 가능
}
}
NumberBox<Integer> intBox = new NumberBox<>(); // OK
NumberBox<Double> doubleBox = new NumberBox<>(); // OK
NumberBox<String> stringBox = new NumberBox<>(); // 컴파일 에러!
4.2 와일드카드를 활용한 유연성 확보
class DataProcessor {
// 모든 타입의 Box 읽기 가능
public static void printBox(Box<?> box) {
System.out.println(box.getItem());
}
// Number의 하위 타입만 가능
public static void processNumbers(Box<? extends Number> box) {
Number num = box.getItem(); // 안전한 타입 변환
System.out.println(num.doubleValue());
}
// Integer의 상위 타입만 가능
public static void addInteger(Box<? super Integer> box) {
box.setItem(10); // Integer 타입 저장 가능
}
}
5. 실전 활용 예시
제네릭을 활용한 데이터 구조 구현
class GenericStack<T> {
private ArrayList<T> elements;
private int size;
private static final int DEFAULT_CAPACITY = 10;
public GenericStack() {
elements = new ArrayList<>(DEFAULT_CAPACITY);
}
public void push(T item) {
elements.add(item);
size++;
}
public T pop() {
if (isEmpty()) {
throw new EmptyStackException();
}
return elements.remove(--size);
}
public boolean isEmpty() {
return size == 0;
}
public T peek() {
if (isEmpty()) {
throw new EmptyStackException();
}
return elements.get(size - 1);
}
}
// 다양한 타입으로 스택 생성 가능
GenericStack<String> stringStack = new GenericStack<>();
GenericStack<Integer> intStack = new GenericStack<>();
GenericStack<Student> studentStack = new GenericStack<>();
이러한 제네릭의 활용을 통해
- 컴파일 시점에서의 타입 체크로 런타임 에러 방지
- 타입 캐스팅 제거로 코드 간결성 확보
- 알고리즘의 재사용성 향상
- 타입 특화된 메서드 구현 가능
- 컬렉션 프레임워크의 타입 안정성 보장
등의 이점을 얻을 수 있으며, 현대 Java 프로그래밍에서 필수적인 요소로 자리잡았다.
'Dev Lang > JAVA' 카테고리의 다른 글
[JAVA] 자바의 기본형과 참조형의 값 공유 특성 (1) | 2024.12.28 |
---|