Object와 T(제네릭)의 차이는 코드 설계 및 타입 안정성, 그리고 사용성에서 중요한 의미를 가집니다. Object로 처리했을 때와 T를 사용했을 때의 차이점과 그 이유를 명확히 설명드릴게요.
1. Object와 T의 주요 차이점
1.1. Object의 특징
- 핵심 특징:
- 자바의 최상위 클래스이므로 모든 객체를 받을 수 있습니다.
- 다형성을 통해 타입 변환을 수행할 수 있습니다.
- 컴파일 단계에서는 타입 검증을 하지 않고 런타임에 타입 캐스팅을 수행해야 합니다.
- 장점:
- 제네릭을 사용하지 않아도 되므로 간단하게 설계할 수 있습니다.
- 모든 데이터를 받아 처리할 수 있어서 유연성이 높습니다.
- 단점:
- 타입 안정성이 부족: 런타임에서 잘못된 타입으로 캐스팅할 경우 ClassCastException이 발생합니다.
- 읽기 어려운 코드: 코드 작성에 명확한 타입이 드러나지 않아서 이후 유지보수 시 가독성을 해칩니다.
예) Object를 사용하는 경우:
public class CacheService {
public Object putCache(String key, Object value) {
// 모든 타입 수용 가능
System.out.println("Cached: " + key + " = " + value);
return value;
}
}
CacheService cacheService = new CacheService();
List<String> listValue = List.of("Apple", "Banana");
Object cachedValue = cacheService.putCache("key", listValue);
// 불편한 타입 캐스팅 필요
@SuppressWarnings("unchecked")
List<String> castedValue = (List<String>) cachedValue; // 넘겨받은 값을 다시 캐스팅
- 타입 캐스팅 문제: cachedValue를 읽어올 때 다시 List<String> 타입으로 변환하기 위해 강제 캐스팅이 필요합니다. 만약 잘못된 타입으로 캐스팅을 시도하면 런타임에서 에러가 발생합니다.
1.2. T(제네릭)의 특징
- 핵심 특징:
- 컴파일 시 타입을 결정합니다(= 타입 안정성을 확보).
- 설계자가 제네릭 매개변수를 통해 타입을 제한하거나 자유롭게 구현할 수 있습니다.
- 코드에서 타입을 명확히 드러낼 수 있어서 가독성이 높아집니다.
- 장점:
- 타입 안정성 확보: 컴파일 시 타입이 검증되므로 런타임 에러를 예방할 수 있습니다.
- 캐스팅 불필요: 값을 사용할 때 별도의 캐스팅이 필요하지 않아 편리합니다.
- 코드 가독성 증가: 호출되는 메서드의 타입이 명확히 드러납니다.
- 단점:
- Object에 비해 구현이 조금 더 복잡(제네릭 선언이 필요합니다).
- 특정 타입에 고정되므로 유연성이 떨어질 수 있습니다.
예) T를 사용하는 경우:
public class CacheService {
// 제네릭 메서드 정의
public <T> T putCache(String key, T value) {
System.out.println("Cached: " + key + " = " + value);
return value;
}
}
CacheService cacheService = new CacheService();
List<String> listValue = List.of("Apple", "Banana");
// 제네릭 덕분에 타입 명시 불필요. 자동으로 List<String> 추론
List<String> cachedValue = cacheService.putCache("key", listValue);
// 타입 캐스팅 필요 없음
System.out.println(cachedValue.get(0));
- 타입 캐스팅 필요 없음: 값이 List<String> 타입으로 자동 추론되므로 캐싱 후에도 안전하게 사용할 수 있으며, 캐스팅 작업이 생략됩니다.
2. Object와 T의 근본적인 차이
2.1. 제네릭은 컴파일 타임에 확인
제네릭을 사용하면 컴파일 시점에 타입이 결정됩니다. 컴파일러는 이 타입을 변수, 반환값 등에 자동으로 적용하며, 잘못된 타입 사용 시 컴파일 에러를 발생시킵니다.
예를 들어:
CacheService cacheService = new CacheService();
cacheService.putCache("key", "StringValue"); // 정상
cacheService.putCache("key", 123); // 정상
cacheService.<List<String>>putCache("key", List.of("Apple", "Banana")); // 제네릭 수동 설정
반면에 Object는 컴파일 단계에서 타입 체크를 하지 않기 때문에, 타입이 맞지 않아도 컴파일은 통과되며, 잘못된 캐스팅은 런타임 에러로 이어질 수 있습니다.
3. 런타임 에러의 가능성
Object 사용 시 문제
다음과 같은 시나리오를 생각해볼 수 있습니다:
CacheService cacheService = new CacheService();
cacheService.putCache("key", List.of("Apple", "Banana")); // List<String> 저장
// 다른 코드가 해당 키에 Integer 값을 덮어씀
cacheService.putCache("key", 123);
// 값을 가져오는 과정에서 강제 캐스팅 문제 발생
@SuppressWarnings("unchecked")
List<String> cachedValue = (List<String>) cacheService.putCache("key", null); // ClassCastException 발생
위 코드에서 첫 번째 putCache는 List<String>을 저장했지만, 두 번째 호출에서 Integer 값을 덮어씌웠습니다.
Object를 사용하면 이런 상황을 막을 수 있는 장치가 없습니다.
T 사용 시 문제 방지
제네릭을 활용할 경우, 컴파일러가 같은 키에 서로 다른 타입을 넣는 것을 방지합니다:
CacheService cacheService = new CacheService();
List<String> listValue = List.of("Apple", "Banana");
cacheService.putCache("key", listValue); // List<String> 타입 고정
cacheService.putCache("key", 123); // 컴파일 에러 발생!
4. 제네릭에 추가적인 타입 제한
제네릭을 사용할 때 타입 안정성을 더 강화하고 싶다면 **타입 제한(bound)**을 추가할 수 있습니다. 예를 들어, 해당 캐시에 저장할 데이터가 특정 인터페이스나 클래스 계층 구조를 상속받아야 한다면 다음과 같이 정의할 수 있습니다:
// T는 반드시 Serializable을 구현한 클래스여야 함
public <T extends Serializable> T putCache(String key, T value) {
System.out.println("Cached: " + key + " = " + value);
return value;
}
이렇게 하면 Serializable을 상속받지 않는 클래스는 캐시에 저장할 수 없게 됩니다.
5. Object vs T 요약 비교
항목ObjectT(제네릭)
| 타입 체크 시점 | 런타임 | 컴파일 타임 |
| 타입 명시 | 없음 | 명확하게 타입 지정 가능 |
| 캐스팅 필요 여부 | 강제 캐스팅 필요 | 필요 없음 |
| 타입 안정성 | 낮음 (런타임 오류 가능성) | 높음 (컴파일 시점 에러 검출) |
| 유연성 | 높음 (모든 타입 수용) | 중간 (타입 제한 가능) |
| 가독성 | 낮음 | 높음 |
6. 선호하는 방식
- 유연성이 중요한 경우: 여러 타입 데이터를 매우 자유롭게 처리해야 하는 경우라면 Object를 사용할 수 있습니다.
그러나 런타임 오류 가능성을 최소화하기 위해 적합한 타임 체크 로직을 추가해야 합니다. - 안전성과 가독성이 중요한 경우: 대부분의 현대 자바 프로그램에서는 **제네릭(T)**이 선호됩니다. 제네릭을 사용하면 코드 안전성과 가독성을 높이고, 런타임 오류를 방지할 수 있습니다.
'DEV Heart' 카테고리의 다른 글
| 분산락 DistributedLock (1) | 2026.03.17 |
|---|---|
| 메세징! Kafka vs SQS (0) | 2026.03.17 |
| Elastic search (0) | 2023.06.23 |
| javax.validation @어노테이션 (0) | 2021.12.27 |
| 초간단 Spring 프로젝트 생성 + dependencies 추가 (0) | 2021.12.20 |