본문 바로가기

Programming/JAVA

java : Generic - 와일드카드

public void takeAnimals(ArrayList<Animal> animals) {
for(Animal animal : animals){
animal.eat();
}
메소드에서 인자값으로 Generic (유형매개변수) 을 사용시에는 다형성이 허용되지 않습니다.
즉, 위에서 보는 것과 같은 메소드는 인자로 반드시 ArrayList<Animal> 타입만 허용됩니다.

만약 Animal 을 상속받는 Human, Lion, Tiger 같은 클래스들만 모아두고 generic 을 각개로 선언했다면
저 메소드를 호출하면서 사용할 수 있는 파라미터가 되지 못합니다.
(ArrayList<Human>, ArrayList<Lion>, ArrayList<Tiger> 등을 파라미터로 쓰질 못한다는 것입니다.
 세 클래스 모두 Animal을 extends 하고 있다 하더라도 말이죠. 굳이 쓰려한다면 컴파일 익셉션이 기다립니다.)

다형성에 의한다면 이는 참으로 잘못된 문법으로 보입니다.
하지만 형 안정성에 의거하여 본다면 무척 올바른 방향제시입니다.

만약에 저 메소드가 다른 타입의 객체를 add 하는 메소드라면 어떨까요? 이렇게 말이죠.
public void takeAnimals(ArrayList<Animal> animals) {
animals.add(new Tiger()); 
}

위와 같은 메소드는 문법상 문제가 없습니다. 그리고 당연히 가능한 코드이기도 하구요.
하지만 저 메소드를 호출하는 클라이언트 코드의 입장에서는 생각해볼만한 문제가 생깁니다.
만약 저 메소드에 인자로 ArrayList<Human> 타입의 객체를 넣었다고 가정해봅시다.
그렇다면 Human 타입의 객체들로만 채워진 자료구조인게 분명한데
그 자료구조안에 Tiger라는 타입의 객체를 요소로 넣겠다는 코드가 되어버립니다.
클라이언트 코드단에서는 이 메소드가 어떤 메소드인지 알고 작성함에 분명하겠지만 훗날 이 메소드가
자료구조의 요소들을 변경하는 메소드가 될 요지가 있다면 분명 엄청난 파장을 일으키는 코드가 되버릴게 뻔하겠죠.

이러한 이유로 메소드의 인자 generic은 다형성을 허용하지 않습니다.
(배열 역시도 같은 문제점을 일으킬 요지가 있지만 컴파일은 가능합니다.
 대신, 런타임에서 ArrayStoreException이 발생합니다. 쩝... 뭐든 컴파일 타임의 익셉션이 좋은데 아쉽네요.) 

하지만 프로그래머 입장에서는 좀 억울합니다. Generic으로 형 안정성을 준 것은 좋으나
작성하는 메소드는 분명 자료구조를 변경할 만한 요인을 가지고 있지 않은 메소드인데 다형성을 못쓰다니 말이죠.
(add 같은 거 안하고 그냥 저~위에 작성한 메소드처럼 귀여운 동물들에게 밥줄건데 말이죠.
 우리집은 강아지만 키우는데 강아지 밥주는 메소드를 쓰기위해 동물원을 만들어 가져다 줄 순 없는 노릇이잖아요.)

이를 위해 제시된 해결방법이 바로 와일드카드입니다. 

public void takeAnimals(ArrayList<? extends Animal> animals) {
for(Animal animal : animals){
animal.eat();
}
위 메소드는 Animal 을 상속받는 모든 객체들을 유형으로 사용하는 ArrayList를 인자로 받을 수 있습니다.
예전에 포스팅했던 T, E와 같은 그냥 식별자처럼 보이는데 이상하죠?
이는 JVM 고마운 녀석이 특별대우를 해주기때문에 문제가 발생하지 않습니다.
어떤 특별대우냐면, 와일드카드가 적용된 자료구조 변수는 더이상 add, addAll 등과 같은 메소드를 통해서
내부 요소들이 변경될 요인을 컴파일때부터 차단해줍니다.
따라서, 와일드카드를 사용한 Generic을 사용하는 자료구조를 인자로 쓰는 메소드(-_-;;;) 는 형 안정성이 보장됩니다.

추가로 또 작성하자면
1번. public <T extends Animal> void takeThing(ArrayList<T> list)
2번. public void takeThing(ArrayList<? extends Animal> list)
위 두 메소드는 동일합니다.(물론 안의 알고리즘도 똑같이 구현해야겠죠^^;;;)
차이점은 1번 메소드같은 경우엔 유형매개변수를 extends Animal 로 작성할 시에
메소드 앞부분에 이미 정의를 해주었기 때문에 뒤에 인자들은 ArrayList<T> list 처럼 간략히 작성할 수 있습니다.
몇개가 되던 같은 의미의 인자라면 변수네임만 달리해주며 여러번 작성할 수 있습니다.
하지만, 2번 메소드 같은 경우에는 인자가 몇개가 되었던, 반복적으로 똑같이 작성해주어야 합니다.


출처 : Headfirst java 완전 베낌..... 저작권 법에 어긋나기전에 코멘트 주시면 바로 수정하겠습니다!!! ㅎㅎ;;