위 아이템에서는 배열과 제네릭 타입의 차이점을 다루고 있으며 결과적으로 배열보다 제네릭 사용을 권유하고 있다.
1. 배열은 공변, 제네릭은 불공변
공변과 불공변?
공변 : 함께 변한다. 즉, 관계(ex 상속..)를 맺는 클래스/타입들이 관계가 유지된 채로 함께 변한다.
Object[] objectArray = new Long[1];
objectArray[0] = "스트링";
System.out.println(objectArray[0]);
//in Runtime : Exception in thread "main" java.lang.ArrayStoreException
"Long은 Object를 상속하고 있다.
배열로 사용되고 있기 때문에, Long에 String type을 넣어도 컴파일이 가능하다."
불공변 : 함께 변하지 않는다. 즉, 관계가 유지되지 않는다.
List<Object> ol = new ArrayList<Long>();
"Long은 Object를 상속하고 있다.
제네릭 타입을 사용하는 경우 같은 타입이 아니므로 컴파일 에러가 발생한다."
2. 배열은 실체화, 제네릭은 비실체화
실체화와 비실체화?
실체화 : 런타임에도 자신이 담기로 한 원소의 타입을 인지하고 확인한다. 즉, 런타임 시점에 타입을 인지하고 실행한다.
비실체화 : 런타임 시점에는 타입 정보를 소거한다. 제네릭 타입 시스템의 취지는 컴파일 시점에 에러를 잡고 런타임에 ClassCastException이 발생하는 것을 막아주는 것이다.
class Test {
public static void main(String[] args) {
List<String> strs = new ArrayList<>();
strs.add(1);
strs.add(2);
String str1 = strs.get(0); // 형변환이 필요없다
String str2 = strs.get(1); // 형변환이 필요없다
}
}
위 Test.java 파일을 컴파일하고, 컴파일한 Test.class 파일을 디컴파일하면 아래와 같은 결과를 볼 수 있다.
디컴파일 한 결과 (Test.java -> Test.class -> decompile)
class Test {
Test() {
}
public static void main(String[] var0) {
List var1 = new ArrayList(); // 타입 정보가 없다. 제네릭이 사라졌다..?
var1.add(1);
var1.add(2);
String var2 = (String)var1.get(0); // 형변환이 추가
String var3 = (String)var1.get(1); // 형변환이 추가
}
}
위에서 알 수 있듯이, 제네릭은 런타임 시점에 타입 정보가 사라진다.
배열은 제네릭 타입, 매개변수화 타입, 타입 매개변수로 사용할 수 없다.
- 따라서 new List<E>[], new List<String>[], new E[]과 같이 제네릭 배열이 생성되면 컴파일 오류를 내야 한다.
- 만약 제네릭 배열 생성을 허용한다면 어떻게 될까? 컴파일러가 자동으로 형변환코드를 생성해 형변환을 진행하는 과정에서 런타임에 ClassCastException이 발생할 수 있다. -> 이는 런타임에서 CCE가 발생하는 일을 막아주겠다는 제네릭 타입 시스템 취지에 어긋난다.
실체화 불가 타입?
실체화되지 않아서 런타임에는 컴파일 타입보다 타입 정보를 적게 가지는 타입을 의미한다.
- E, List<E>, List<String>등과 같은 타입을 실체화 불가 타입이라 한다.
앞에서 배웠던 매개변수화 타입 중, 비한정적 와일드카드 타입 "?"만 List<?>, Map<?,?>등의 형태로 실체화를 할 수 있다고는 하지만, 유용하게 쓰일 일은 없다고 한다.
- 배열을 제네릭으로 만들수 없어서 안좋은 점도 있다.
- 제네릭 컬렉션에서 자신의 원소 타입을 담은 배열을 반환하고 싶은 경우
- 보통 불가능하다.
- item33에 가면 해결될~~수도~~
- 제네릭 타입과 가변인수 메서드를 함께 쓰면 해석이 어려운 경고 메시지를 받을 수 있다.
- 가변인수 메서드를 호출할 때마다 이를 담을 배열이 만들어지는데, 이 때 그 배열 원소가 실체화 불가 타입일 수가 있다.
- 이 때 경고가 발생한다.
- @SafeVarargs annotaion을 사용해 대처 가능
- 제네릭 컬렉션에서 자신의 원소 타입을 담은 배열을 반환하고 싶은 경우
- 배열로 형변환을 하던 중 제네릭 배열 생성 오류나 비검사 형변환 경고가 떴다면?
- 대부분 배열 E[] 대신 컬렉션 List<E>를 사용하면 해결된다. 해봐라~!
- 코드는 조금 복잡해지고 성능이 나빠질 수는 있다.
- 그러나, 타입 안정성과 상호 운용성이 좋아진다.
- 대부분 배열 E[] 대신 컬렉션 List<E>를 사용하면 해결된다. 해봐라~!
핵심 정리
배열 제네릭 공/불공변 공변 불공변 실체/비실체화 실체화 비실체화(타입정보 소거) 타입안정성 컴파일타임에는 타입안전X
런타임에는 타입안전O컴파일타임에는 타입안전O
런타임에는 타입안전X결론 따라서 배열과 제네릭을 섞어 쓰기란 쉽지 않다.
둘을 쓰다가 컴파일 오류나 경고를 만나면, 제네릭으로 바꿔라!
'JAVA > Effective Java' 카테고리의 다른 글
item 30. 이왕이면 제네릭 메서드로 만들어라 (0) | 2022.05.14 |
---|---|
item 29. 이왕이면 제네릭 타입으로 만들어라 (0) | 2022.05.13 |
item 27. 비검사 경고를 제거하라 (0) | 2022.05.10 |
item 26. 로 타입은 사용하지 말라 (0) | 2022.05.10 |
item 25. 톱레벨 클래스는 한 파일에 하나만 담아라 (0) | 2022.05.09 |
댓글