본문 바로가기
PROGRAM/effective JAVA

Generic - 원천(raw)타입을 사용하지 맙시다.

by ojava 2011. 4. 13.
반응형

이 카테고리에 쓰는 글은 동명의 책인 <effective JAVA>에 대한 공부 내용을 정리하여 올리는 곳입니다.




Java 1.5 이후 버전부터 지원되는 내용으로는 매우 다양한 기능들이 포함되어 있습니다.
for문의 기능 확장으로 forEach 방식의 활용이 가능해졌으며, AutoBoxing, UnBoxing 등..
그리고 오늘부터 몇 주간 포스팅하게 될 내용인 Generic에 대한 내용을 지원합니다.

Generic이란 무엇인지 알아보기 전에 우선 제목에 있는 원천 타입에 대한 내용을 알아봅시다.

원천타입은, Collection interface로부터 파생된 subInterface들인 
BlockingDeque<E>, BlockingQueue<E>, Deque<E>, List<E>, NavigableSet<E>, Queue<E>, Set<E>, SortedSet<E>과 같은
타입들에서 꺽쇠괄호 안에 있는 Element 들을 포함하지 않은 원래 그대로의 모습을 말합니다.
예를 들어, Set<E>의 원천타입은 Set을 뜻하는 것입니다.

아래와 같은 내용들이 원천타입입니다. 단순하게 < > 를 통해 Element를 정해주지 않는다는 개념인거죠.
Set, List, Map, SortedSet, SortedMap, HashSet, TreeSet, ArrayList, LinkedList, Vector, Collections, Arrays ....


그렇다면 간단하게 생각해봅시다.
Generic이라는 개념에 대해서 말하자면 어떠한 Collection에 어떤 타입으로 들어간다고 문자로서 알려준다고 할 수 있습니다.
=> 이는 다른 말로 하나 이상의 타입 매개변수를 선언한 것을 말하지용.

프로그래머가 아무리 주석을 통해서 "이 List에는 String값만 넣어주세요!"
라고 말을 한다고 해도 실수로 String이 아닌 그 이외의 값을 넣게 되는 것을 지금까지의 Java는 바로 잡아주지 못했습니다.
당연히 주석은 읽는 '사람'에게만 보이는 말이지 'Java'에게 보이는 말은 아니기 때문이지영.

하지만 Generic을 도입하게 됨으로써 Java는 더욱 똑똑해졌습니다. 오예~


List <E>     ex) List <String>

위와 같은 내용은 프로그래머가 Java의 List에게, "String 아니면 받지도 마!" 라고 직접 얘기해주는 겁니다.
따라서 String 등 지정해 준 타입이 아니면 Java는 "너 울 엄마가 들어오지 말래~" 하고 Error를 툭하고 던지게 되는 겁니다.

또한 Generic 형식으로 지정한 내용에 대해서는 읽어올 때 캐스팅해서 불러올 필요가 없습니다~
그 안에 어떤 타입으로 저장되어 있는지 알기 때문이죠.

즉~ Generic을 사용하면 안전성 (지정한 타입만 넣게 된다는) 과 표현력 (캐스팅 없이도 사용가능) 이라는 두 장점을 가지게 됩니다.


But...... String과 같은 것이 아닌 정형화되지 않은 타입을 집어넣을 수 있는 Generic도 존재합니다.
그게 무슨말이냐구요 ㅠ_ㅠ? 특정한 타입 매개변수가 아닌 <?> <Object> 를 통해서도 Generic이 가능하다는 얘기지영. 
지금까지 어떤 특정한 타입 매개변수를 썼던 방식을 매개변수화 타입이라고 부른답니다.

그럼 <?> 요렇게 사용하는 타입은?????? 언바운드 와일드 카드 타입이라고 합니다.
이름 참 길죠! 이 타입이 나오게 된 이유는, Generic을 사용하여 안전성 및 표현력을 높이고는 싶으나
특정한 어떤 값이 들어갔는 지를 모르는 경우, 또는 어떤 타입이 들어가도 상관없는 경우에 사용됩니다.

이는 원천타입보다는 당연히 안전하고, 유연성이라는 측면에서도 긍정적입니다.


<Obejct>는 매개변수화 타입에 해당되지만, 어떠한 타입이라고 딱 단정지은 경우와 비교하면
Object라는 특성이 모든 것의 상위이므로! 어떤 객체도 추가할 수 있다는 장점이 있습니다.


이제 그럼 제목에서 왜 우리가 원천타입을 사용하면 안된다고 하는 지에 대한 가장 큰 이유를 예를 통해 알아봅시당.



★그것은 바로바로 안전성★ 


지금까지 계속 얘기해왔지만 안전성이라는 측면이 원천타입이 아닌 Generic 이용의 가장 큰 이유입니다.
정확하게 어떻게 안전하다는 얘기인지 예시를 보시겠습니다.


// 원천 타입 (List)을 사용하면, 런타임 시에 에러가 발생하는 코드입니다.

public static void main (String [] args) {

List<String> strings = new ArrayList<String> ();
unsafeAdd(strings, new Integer(42));
String s = strings.get(0); // 컴파일러가 캐스트 코드 생성

}

private static void unsafeAdd(List list, Object o) {

list.add(o);




위와 같은 내용은 원천 타입을 사용한 예로서 이 소스코드는 컴파일은 되지만,
런타임 시에 아래와 같은 오류 메시지를 보여주게 됩니다.

Test.java:10 : warning; unchecked call to add(E) in raw type List
list.add(o);


이는 List<String> 이라는 매개변수화로 strings를 선언해두고 unsafeAdd라는 메소드에서 원천타입의 List를 만들어
add를 하고 있습니다. 하지만 여기서 문제는 String이라는 실 매개변수를 받고 있는 strings List에 42라는 int 타입을 넣으려고 했다는 점이죠.

이 코드가 컴파일이 된 이유는 unsafeAdd의 List list 가 원천타입이기 때문에 List에 int 타입을 넣더라도
컴파일 시에 문제가 뭔지 모르고 넘어가버리기 때문입니다 ㅠ_ㅠ 여기서 원천타입의 문제가 드러납니다.


코드가 돌아가지 않는 이유를 컴파일 시에 잡지 못하기 때문에, 코드 수정에 대한 도움을 컴파일러로부터 받을 수 없고
런타임 에러를 보고 스스로 문제가 무엇인지 찾아나가야 한다는 점입니다.
안전성도 떨어지고, 코드를 짜는 데 대한 편리성 및 효율성이 떨어지는 것이지요.



But, 여기서 unsafeAdd(List list, Object o) ---->>>> unsafeAdd(List<Object> list, Object o) 로 변환해주면
아래와 같은 컴파일 에러를 만날 수 있습니다.

Test.java:5 : unsafeAdd(List<Object>, Object) cannot be applied to (List<String>, Integer)
unsafeAdd(strings, new Integer(42));


String으로 선언된 매개변수화 타입에 Integer 타입을 대입하려고 했다. 5번째 라인에서 발생했다.
이와 같이 무엇때문에 에러가 발생했고, 몇번째 라인에서 잘못된 내용을 기술하였다고 컴파일러가 도와주고 있습니다.


요약하면 원천타입을 사용하여 잘못된 타입을 코드에 작성할 경우 그 내용에 대한 에러를 런타임 시에 잡게 되지만
Generic을 사용하였을 경우에는, 그러한 오류를 컴파일 시 즉시 잡아낼 수 있다는 점에서 안전성을 보장한다.



거기다 추가적으로 캐스팅을 할 필요없이 내용을 불러들일 수 있다는 점 등이 있지만,
이것은 매개변수화 타입의 경우 실 매개변수를 지정하는 방식이므로 이해하실거라 넘어가고 생략.


그렇다면 왜!!!!!!! 아직까지 Java는 원천타입을 지원해서 개발자를 헷갈리게 만드는가!!!!!!
쓰지 말라고 권고했으면 아예 지워야하는거 아닌감? 응? 잉? 앙?


그 이유는 바로 1.5 버전 이전으로 작성된 코드와의 호환성 때문이다.

원천타입에 대한 지원을 하지 않게 되면 그 이전에 작성된 코드들이 실행될 수 없게 되버리기 때문에
여전히 원천타입에 대한 지원을 계속하고 있는 것. 하지만 1.5 이후의 버전을 사용하는 사람들이라면
원천타입보다 Generic 방식을 이용하는 것이 더 안전하고 효과적이라는 것을 알았으므로 
가급적이면 Generic을 이용합시당!



2011.04 어려운 effective JAVA 책을 붙들고ㅠㅠ 오혜영 작성
반응형