정리

타입 안정성을 지키고 컴파일 에러를 보장하기 위해 로 타입이 아닌 제네릭 타입을 사용하라!

제네릭 타입이란?

클래스/인터페이스에 <타입 매개변수>를 붙인 타입을 의미한다. 제네릭 클래스는 클래스<타입 매개변수>, 제네릭 인터페이스는 인터페이스<타입 매개변수> 형태이다.

  • 제네릭 예시
    • List<E> → E는 정규 타입 매개변수
    • List<String> → String은 실제 타입 매개변수

 

제네릭 타입 사용 시 이점

private final Collection<Stamp> stamps = ...;
  • 타입 안정성
    • 컴파일러가 stamps에는 Stamp 인스턴스만 넣어야함을 인지함
    • stamps에 다른 타입의 인스턴스를 넣으려할 때 컴파일 에러 발생
  • 표현력

 

로 타입

List와 같이 타입 매개변수가 없는 제네릭 타입을 의미

  • 로 타입 사용을 하면 안되는 이유
    • 타입 안정성과 표현력이 없음
    • 런타임 에러 가능성 존재
  • 예시 코드
    • 에러 발견 시기가 컴파일 시점이 아닌 런타임 시점이다.
    // 로 타입
    private final Collection stamps = {...};
    stamps.add(new Coin(...)); 
    
    // 위 코드에서 발생할 수 있는 문제점
    add() 하는 시점에는 에러가 발생하지 않지만
    추후에 런타임 에러 가능성이 있고, 원인 코드를 추적하기가 어려워짐
    
    // 해결책
    private final Collection<Stamp> stamps = {...}; -> 제네릭으로 타입 안정성 확보
    stamps.add(new Coin(...)); -> 컴파일 에러 발생
    

 

로 타입이 만들어진 이유

제네릭이 나오기 전 코드와의 호환성을 위해 로 타입이 만들어졌다.

List vs. List<Object>

  • List
    • 제네릭 타입을 배제한다는 것을 의미
    • List 타입 매개변수에 List<String> 넘길 수 있음 → 타입 안정성 보장되지 않음
public static void main(String[] args) {
		List<String> strings = new ArrayList<>();

		// List 에 List<String> 넘길 수 있음
		unsafeAdd(strings, Integer.valueOf(42));
		strings.get(0); // 컴파일러가 자동 형변환
}

// 매개변수에 로 타입 List 존재
private static void unsafeAdd(List list, Object o) {
		list.add(o);
}
  • List<Object>
    • 모든 타입(Object)을 허용한다는 의사를 컴파일러에 명확히 전달한다는 것을 의미
    • List<Object> 타입 매개변수에 List<String> 넘길 수 없음 → 타입 안정성 보장, 무공변성?
      • List<Object>, List<String>
    public static void main(String[] args) {
    		List<String> strings = new ArrayList<>();
    
    		// List<Object> 에 List<String> 넘길 수 없음
    		safeAdd(strings, Integer.valueOf(42)); -> 컴파일 에러 발생
    }
    
    // 매개변수에 제네릭 타입 List<Object> 존재
    private static void safeAdd(List<Object> list, Object o) {
    		list.add(o);
    }
    

→ 로 타입 말고 제네릭 타입 사용해라

비한정적 와일드 카드 <?>

제네릭 타입을 쓰고 싶지만, 실제 타입 매개변수가 무엇인지 신경 쓰고 싶지 않을 때 사용하는 타입이다.

Set<?>과 같은 형태로 사용하면 된다. 비한정적 와일드 카드를 사용하면 타입 안정성을 보장하고, 유연한 코드를 작성할 수 있다.

 

 

로 타입을 사용하는 예외 케이스

  1. class 리터럴에는 로 타입을 사용하라
    1. List.class, String[].class, int.class 사용 가능
    2. List<String>.class, List<?>.class 사용 불가능
    Class<List> listClass = List.class; // 가능
    List<?>.class; // 불가능
    
  2. instanceof 연산자
    1. 런타임에는 제네릭 타입 정보가 지워지므로 <?>(비한정적 와일드카드 타입) 외에 매개변수 타입은 instanceof 사용에 적용할 수 없다.
    if (o instanceof Set) { // 로 타입
    	Set<?> s = (Set<?>) o; // (로 타입 -> 와일드 카드 타입) 형변환
    }
    

+ Recent posts