개발을 하다 보면 다양한 타입의 데이터를 안전하게 저장하고 꺼내는 기능이 필요하다. 이때 많은 초보 개발자들이 처음에는 IntegerBox, StringBox처럼 각각의 타입에 맞춘 클래스를 따로 만들어 사용한다. 하지만 이러한 방식은 코드의 재사용성이 떨어지고, 타입이 추가될수록 클래스가 기하급수적으로 늘어난다는 문제를 안고 있다.
이번 글에서는 이런 문제점을 실제 코드로부터 출발하여 어떻게 해결해 나갈 수 있는지를 단계별로 보여준다. 그리고 마지막에는 이 문제들을 해결하기 위해 왜 제네릭(Generic)이 필요한지, 제네릭이 어떤 장점을 제공하는지 자연스럽게 이해할 수 있도록 구성했다.
1. 타입별로 클래스를 만들면 생기는 문제
먼저, 숫자와 문자열을 담는 클래스를 각각 만들어보자.
// IntegerBox.java
public class IntegerBox {
private Integer value;
public void set(Integer value) {
this.value = value;
}
public Integer get() {
return value;
}
}
// StringBox.java
public class StringBox {
private String value;
public void set(String value) {
this.value = value;
}
public String get() {
return value;
}
}
이 코드들은 각 타입에 대해 별도의 클래스를 만들어야 한다는 문제점이 있다. DoubleBox, BooleanBox, CharacterBox 등 다양한 타입을 담기 위해 클래스를 계속 만들어야 한다면 유지보수 비용이 커지고, 코드의 중복도 심해진다.
2. 중복 제거를 위해 Object 활용하기
중복을 줄이기 위해 다형성을 활용한 다음과 같은 ObjectBox를 만들 수 있다.
// ObjectBox.java
public class ObjectBox {
private Object value;
public void set(Object value) {
this.value = value;
}
public Object get() {
return value;
}
}
사용 예시는 다음과 같다.
// BoxMain2.java
public class BoxMain2 {
public static void main(String[] args) {
ObjectBox integerBox = new ObjectBox();
integerBox.set(10); // 오토박싱: int → Integer → Object
Integer integer = (Integer) integerBox.get(); // 다운캐스팅
System.out.println("integer = " + integer);
ObjectBox stringBox = new ObjectBox();
stringBox.set("hello");
String string = (String) stringBox.get(); // 다운캐스팅
System.out.println("string = " + string);
}
}
문제점: 타입 안정성이 없다
Object는 모든 클래스의 최상위 타입이기 때문에 어떤 값이든 저장할 수 있다. 하지만 꺼낼 때는 반드시 다운캐스팅(casting) 해야 하고, 만약 잘못된 타입으로 꺼내면 런타임 오류가 발생한다.
예를 들어 다음과 같은 실수를 저지를 수 있다:
ObjectBox integerBox = new ObjectBox();
integerBox.set("hello");
Integer wrong = (Integer) integerBox.get(); // 컴파일 오류 없음, 런타임에서 ClassCastException
컴파일 시점에는 오류가 없기 때문에 문제가 나중에야 발견된다. 결국 코드는 재사용 가능하지만 안전하지 않다는 문제가 생긴다.
3. 제네릭(Generic)으로 해결하기
위의 두 가지 방식은 서로 장단점이 있다.
- IntegerBox, StringBox → 타입 안전하지만, 재사용성 낮음
- ObjectBox → 재사용성 높지만, 타입 안정성 낮음
이 두 가지 문제를 동시에 해결하기 위해 자바는 제네릭(Generic) 기능을 제공한다.
// GenericBox.java
public class GenericBox<T> {
private T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
<T>는 타입 매개변수(Type Parameter) 로, 클래스 내부에서 T는 실제 타입으로 대체된다.
사용 방법은 다음과 같다.
// BoxMain3.java
public class BoxMain3 {
public static void main(String[] args) {
GenericBox<Integer> integerBox = new GenericBox<>();
integerBox.set(10);
Integer integer = integerBox.get();
System.out.println("integer = " + integer);
GenericBox<String> stringBox = new GenericBox<>();
stringBox.set("test");
String string = stringBox.get();
System.out.println("string = " + string);
GenericBox<Double> doubleBox = new GenericBox<>();
doubleBox.set(10.5);
Double doubleValue = doubleBox.get();
System.out.println("doubleValue = " + doubleValue);
}
}
장점: 컴파일 시 타입 체크 + 캐스팅 불필요
- 컴파일 시점에 타입을 체크하므로, 잘못된 타입을 저장하거나 꺼낼 수 없다.
- 꺼낼 때 형변환(캐스팅)을 하지 않아도 된다.
- 코드 재사용성과 타입 안정성을 모두 확보할 수 있다.
마무리하며: 왜 제네릭이 필요한가?
IntegerBox, StringBox처럼 타입별로 클래스를 만들면 중복 코드가 많고 비효율적이다. 이를 개선하기 위해 ObjectBox처럼 다형성을 활용하면 코드 재사용성은 확보할 수 있지만, 타입 안정성이 떨어지고 런타임 오류의 위험이 생긴다.
자바는 이러한 문제를 해결하기 위해 제네릭(Generic) 기능을 제공한다. 제네릭은 컴파일 시점에 타입을 체크하여 안정성을 확보하고, 다형성과 유사하게 동작하여 중복을 줄이고 재사용성을 높이는 도구이다.
즉, 제네릭은 코드의 편의성을 넘어서 안전성과 재사용성을 동시에 만족시키는 강력한 기능이다.
감사합니다.
'자바' 카테고리의 다른 글
| [JAVA] 배열의 단점과 ArrayList, LinkedList 비교 (1) (4) | 2025.07.17 |
|---|---|
| [JAVA] 자바 제네릭 메서드와 와일드카드, 그리고 타입 이레이저 (1) | 2025.07.16 |
| [JAVA] 예외 처리4 -실무 (4) | 2025.07.12 |
| [JAVA] 예외 처리3 -실습 (1) | 2025.07.11 |
| [JAVA] 예외 처리 2 -이론 (5) | 2025.07.10 |
