반응형

개요

 이번 아이템에서도 생소한 용어인 '로 타입'이 등장했다. 로 타입이 뭐길래 사용하지 말라는 걸까?


로 타입

 제네릭 타입에서 타입 매개변수(괄호 < > 안에 들어가는 타입 값)를 사용하지 않았을 때의 타입을 말한다. List<E>의 로 타입은 List, ArrayList<E>의 로 타입은 ArrayList 이다.

List list = ... // 로 타입!

 


로 타입의 사용 예

 아래는 로 타입을 사용하는 예이다. 컴파일 시 오류가 발생하지 않으나, 런타임 시 타입 오류가 발생한다. Coin을 Money로 캐스팅하려하니 에러가 나는 건 당연하지만 중요한건 이 에러가 컴파일에는 발생하지 않는다는 점이다. 이런 케스팅 에러는 런타임보다 컴파일 타임에 발견되는 것이 좋다.

List list = new ArrayList();

Coin coin = new Coin();
list.add(coin); // 실수로 Coin 인스턴스를 추가하였다.

Money getCoin = (Money) list.get(0); // 런타임 시 ClassCastException이 발생한다.

 


컴파일 타임에 발견되는 캐스팅 에러

 타입 매개변수를 사용한다면 컴파일 타임에에 컴파일러가 오류를 캐치하게 된다. 그럼 변수 초기화 시 타입 매개변수를 사용하면 무조건 해당 변수는 타입 안전성을 갖게되는걸까? 그건 아니다.

List<Money> list = new ArrayList(); // 타입 매개변수 사용

Coin coin = new Coin();
list.add(coin); // 컴파일 에러 발생!

Money getCoin = list.get(0);

 


메서드 파라미터에 사용되는 로 타입

 앞에서 제공한 코드 중간에 unsafeAdd 메서드가 추가되었고, 로 타입으로 list 값을 받고 있다. 이때 list.add(o) 부분에서 과연 컴파일 또는 런타임 에러가 발생할까?

List<Money> list = new ArrayList();

Coin coin = new Coin();
unsafeAdd(list, coin);

Money getCoin = list.get(0);

...

public static void unsafeAdd(List list, Object o){
    list.add(o); // 예외가 발생할까요?
}

 

list 에 대한 타입 매개변수를 Money로 했으니 당연히 list.add(o) 부분에서 컴파일 예러가 발생한다고 생각할 수 있지만 그렇지 않다. 심지어 list.add(o) 시 런타임 예외도 발생하지 않는다. Coin 타입의 인스턴스가 Money 타입의 list에 잘 들어간다. 대신 list.get(0) 을 통해 값을 조회할 때 ClassCastException이 발생한다.

 

 list 변수의 타입 안전성이 unsafeAdd 메서드에 추가한 '로 타입' 매개변수에 의해 파괴되는 순간이다. 

 

List<Money> 타입의 list에 Coin 인스턴스가 들어있는 상황

 

 


로 타입을 사용하면 안되는 이유

 

 💡  제네릭 타입이 제공하는 타입 안정성과 표현력을 굳이 버리는 꼴이다.
제네릭 타입에서 공식적으로 발을 뺀 타입이다.

로 타입 완전 별로인데 그냥 자바 진영에서 제명하면 안돼?

 로 타입을 폐기할 수 없는 이유는 개발자들이 제네릭을 받아들이는데 오랜 시간이 걸렸고, 이미 '로 타입'으로 작성된 코드들이 너무 많았기 때문에, 그 코드와의 호환성을 위해 남겨두고 있는 것이라고 한다.

 


모든 타입을 허용하려면 로타입 말고 Object 타입으로 사용하자

 모든 타입을 허용하는 변수를 정의할때는 정해진 타입이 없으니 로 타입으로 쓸 수 있지만, 제네릭 타입에서 발을 뺀 로 타입을 쓰는 것 자체가 모순이다. 이때는 로 타입 대신 Object 타입을 사용하자.

 

Coin coin = new Coin();
Money money = new Money();

List<Object> list = new ArrayList<>(); // 로 타입 대신 Object 타입
list.add(coin);
list.add(money);

Coin getCoin = (Coin) list.get(0);
Money getMoney = (Money) list.get(1);

 

메서드 파라미터는 타입 매개변수를 Object 타입으로 만들 수 없다.

 모든 타입을 허용하는 메서드를 만들고, 여러 타입 매개변수를 가진 리스트에서 이를 재사용하도록 만들 수 있을까? 실제로 Object 타입을 가진 list를 매개변수로 받는 add 메서드를 구현했더니 컴파일 에러가 발생한다. 컴파일 에러가 발생하는 이유는 List<Object> 타입과 List<Coin> 타입이 다르기 때문이다.

 

List<Money> list = new ArrayList<>();

Coin coin = new Coin();
objectAdd(list, coin); // 컴파일 에러

Coin getCoin = (Coin) list.get(0); // 컴파일 에러


...

public static void objectAdd(List<Object> list, Object o){
    list.add(o);
}

비한정 와일드카드 타입 활용

 컴파일 에러를 해결하기 위해 비한정 와일드카드 타입을 쓸 수도 있다. 하지만 와일드 카드를 사용할 경우 타입 안전성을 지키기 위해 null 외에 아무 값도 넣지 못하게 된다. 즉, 타입 안전성을 훼손하는 모든 작업은 할 수 없는 것이다. 단, get과 같은 작업은 타입 안전성을 훼손하지 않으므로 가능하다.

List<Money> list = new ArrayList<>();

Coin coin = new Coin();

objectAdd(list, coin);

...

public static void objectAdd(List<?> list, Object o){

    list.add(o); // 컴파일 에러
    list.get(0); // 컴파일 에러는 발생하지 않음.
}

 만약 모든 타입에 대해 타입 안전성을 훼손하지 않는 비지니스 로직을 처리해야할 경우 비한정 와일드카드 타입을 활용하겠지만, 그게 아니라면 굳이 모든 타입을 허용하는 메서드를 만들 필요는 없다고 생각한다.


로 타입을 사용하는 예외 상황

 로 타입을 사용해야 하는 상황도 있다.

 

1. class 리터럴

 class 타입이 들어가야하는 자바 문법에 List.class 와 같은 로 타입은 허용하지만, List<String>.class와 같은 매개변수화 타입은 허용하지 않는다.

 

2. instanceof 연산자

 instanceof 연산자는 비한정적 와일드카드 타입과 로 타입 말고는 적용할 수 없다.


정리

 로 타입을 사용하면 런 타임에 예외가 일어날 수 있으니 사용하면 안된다. 로 타입은 제네릭 도입에 따른 호환성을 위해 제공될 뿐이다.

반응형

+ Recent posts