본문 바로가기
DEV Heart

Object랑 T로 받았을떄 무슨 차이야?

by 로띠 2025. 6. 30.

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