맞는데 왜 틀릴까..?

Java/Effective Java

[Effective Java] Item 26. 로 타입은 사용하지 말라

안도일 2023. 5. 9. 16:45

 

용어 정리

 

제네릭 타입 : 제네릭 클래스와 제네릭 인터페이스 ex) List<E>

 

매개변수화 타입 : List<String>은 원소의 타입이 String인 리스트를 뜻하는 매개변수화 타입

 

E : 정규 타입 매개변수 

String : 실제 타입 매개변수

 

로 타입(raw type) : 제네릭 타입에서 타입 매개변수를 사용하지 않은 때 ex) List

  • 타입 선언에서 제네릭 타입 정보가 전부 지워진 것처럼 동작
  • 제네릭이 만들어지기 전 코드와 호환되도록 하기 위해 존재

 

 

로 타입을 사용하지 말아야 하는 이유

 

로 타입을 사용하면 제네릭이 안겨주는 안정성과 표현력을 모두 잃게 된다.

 

아래와 같은 예를 살펴보자.

만약 실수로 Stamp 인스턴스 대신에 Coin 객체를 넣어도 오류 없이 컴파일이 되지만 실제로 Collection에서 객체를 꺼내려할 때 오류가 발생한다.

 

private final Collection stamps = ...

stamps.add(new Coin());..

 

오류 발생!!

 

for (Iterator i = stamps.iterator(); i.hasNext();){
	Stamp stamp = (Stamp) i.next(); //ClassCastException을 던진다
    	stamp.cancel();
}

 

 

안전성 확보

 

 

1. 컴파일러 인지

 

private final Collection<Stamp> stamps // stamps에는 Stamp의 인스턴스만 넣어야 함을 명시

 

  • 컴파일러가 Stamp의 인스턴스만 넣어야 함을 인지할 수 있다.
  • 컴파일러는 컬렉션에서 원소를 꺼내는 모든 곳에 보이지 않은 형변환을 추가해 실패하지 않음을 보장한다.

 

 

2. List vs List<Object>

 

로 타입인 List와 매개변수화 타입 List<Object> 는 무슨 차이일까

 

 

매개변수로 List를 받는 메서드에 List<String>을 넘길 때

 

 

컴파일이 정상적으로 동작하여 unsafeAdd 함수까지 잘 동작하지만 그다음 코드에서 형변환에 실패해 오류를 발생시킨다.

 

 

매개변수로 List<Object>를 받는 메서드에 List<String>을 넘길 때

 

 

컴파일 조차 되지 않아 사전에 안전성을 확보할 수 있다.

 

  • 매개변수로 List를 받는 메서드에 List<String>은 넘길 수 있지만 List<Object>를 받는 메서드에는 넘길 수 없다.
  • List<String>은 List의 하위 타입이지만 List<Object>의 하위 타입이 아니다.
  • List<Object> 같은 매개변수화 타입을 사용할 때와 달리 List 같은 로 타입을 사용하면 타입 안전성을 잃게 된다.

 

비한정적 와일드카드 타입

 

제네릭 타입을 쓰고 싶지만 실제 타입 매개변수가 무엇인지 신경 쓰고 싶지 않다면 비한정적 와일드카드 타입 <?>를 사용하자.

 

모르는 타입의 원소도 받는 로 타입으로 구현

 

 

이 메서드는 동작은 하지만 로 타입을 사용해 안전하지 않다.

아무 원소나 넣을 수 있으니 타입 불변식을 훼손하기 쉽다.

 

 

비한정적 와일드카드 타입으로 구현

 

 

비한정적 와일드카드로 구현한다면 컬렉션의 타입 불변식을 훼손하지 못하게  null을 제외한 어떤 다른 원소도 넣을 수 없다. 

 


로 타입을 사용하면 런타임에 예외가 일어날 수 있으니 사용하지 말자.

 

  • Set<Object>는 어떤 타입의 객체도 저장할 수 있는 매개변수화 타입 (하나의 Set에 서로 다른 객체가 들어갈 수 있다.)
  • Set<?> 모종의 타입 객체만 저장할 수 있는 와일드카드 타입 (하나의 Set에 null을 제외한 다른 객체는 들어갈 수 없다.)
  • 로 타입 Set은 제네릭 타입에 속하지 않는다.
  • Set<Object>와 Set<?>는 안전하지만 로 타입 Set은 안전하지 않다.