티스토리 뷰

Generics는 raw-type Collection, 배열을 사용할 때의 문제를 해결하기 위해 나왔다, 그 문제가 어떤 문제일까?

바로 Runtime 시 발생할 수 있는 ClassCastException을 컴파일 에러로 잡아준다는 것이다

알고리즘 문제를 풀 때는 보통 Collection 보다 배열을 쓰게 되는데 성능 차이가 꽤 크기 때문이다

이와 반대로 웹 개발에는 배열을 쌩으로 쓰는 일은 흔치 않고 컬렉션을 사용한다

성능 차이가 나는데도 컬렉션을 쓰는 이유는 컬렉션과 배열의 차이로 기인한다

 

컬렉션을 사용할 때, 주로 raw-type을 사용하지 않고 Generics와 버무려서 사용하는데 Generics는 invariant, 불공변이다

제네릭을 사용하는 경우 다른 타입으로 지정하면 상속 관계와 상관없이 다른 타입이다

이 말은 String은 Object의 하위 타입이지만 List<String>은 List<Object>와 다른 타입이라는 것이다

 

이와 반대로 배열은 covariant 공변이다, String[]은 Object[]의 하위 타입이다

이로 인해 발생할 수 있는 문제는 Object[]로 업캐스팅하고 실제 구현체는 Object의 하위 타입으로 생성하고

런타임에 구현체로 넣어준 타입과 다른 타입을 넣으려 할 때 발생한다

Long 타입 배열로 실제 인스턴스를 만들고 String 타입을 넣으려 하면 ArrayStoreException을 던진다

Object[] objectArray = new Long[1];
objectArray[0] = "야호";

야호 넣는 순간

 

또 다른 차이점은 제네릭은 런타임 시 타입 정보가 소거된다는 점인데

제네릭이 등장하기 전 코드와의 상호 운용성 때문이라고 한다

컴파일 타임에만 타입 체크를 하고 런타임에는 List<String> -> List로 변환된다

배열은 런타임에도 타입이 유지되기 때문에 reify, 실체화된다고 표현한다

 

제네릭은 불공변, 배열은 공변이기 때문에 얘네를 버무려 사용할 수 없다

따라서 List<String> 타입의 배열 List<String>[], 임의의 타입 E에 대한 배열 E[] 와 같은 코드는 생성할 수 없다

컴파일러가 컴파일 시점에 배열의 실체화되는 타입을 알 수 없기 때문이다

List<String>[] stringLists = new List<String>[1];
List<Integer> intList = List.of(42);
Object[] objects = stringLists;
objects[0] = intList;
String s = stringLists[0].get(0);

 

이펙티브 자바 책에 나온 예시만으로도 이해가 잘 되기 때문에 그대로 가져왔다

만약 List<String>[]이 허용된다면 List<String>[]을 만들고 Object[]로 형변환을 수행한다, 객체 조상님이라 가능하다

Object[]은 모든 걸 받아 줄 수 있으니 List<Integer>를 요소로 넣어준다

List<String> 타입의 stringList는 모든 요소의 타입을 제네릭을 이용해 String으로 지정했다

런타임 시 List로 치환된 stringList에서 요소를 꺼내 자동 형변환을 때려주어 String으로 변환하는데 

stringList[0]은 List<Integer>이고, get(0)은 Integer니까 자동 형변환을 수행할 때 ClassCastException이 터져버린다

제네릭은 런타임 시 발생할 수 있는 ClassCastException을 막기 위해 나온 것이다 라는 점을 기억해보면

그래서 자바에서는 List<String>[]을 허용하지 않는다

 

물론 위와 같이 쌩으로는 사용할 수 없지만 이를 교묘히 피해 Generic Array를 사용하는 두 가지 방법이 있다

이런 방법도 있다는 걸 재미로 알아만 두고 사용은 자제하자

성능이 정말 중요하다면 사용할 수도 있을 듯하다

 

1. 내부에 Object[]를 가지고 있는 CustomArray<E> 클래스를 만든다

CustomArray에서 get으로 꺼내는 과정에 raw-type 사용으로 인한

비검사 경고를 제거하기 위해 @SuppressWarnings를 사용해야 한다

 

2. 같은 방식으로 CustomArray를 만들되 비검사 경고를 제거하기 위해 reflection을 사용한다
reflection으로 인한 보안 구멍, 성능 저하가 뒤따른다

 

이번 아이템에서 핵심은 제네릭과 배열이 어떤 차이가 있는지, 왜 둘을 비벼 먹을 수 없는지에 대해 이해하는 것이다

배열은 런타임에도 타입을 알 수 있게 실체화되고 Super-Sub 관계가 있는 공변이다

제네릭은 런타임에 타입 정보를 알 수 없는 소거 방식을 사용하고 Super-Sub 관계가 없는 불공변이다

댓글
링크
글 보관함
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Total
Today
Yesterday