Generics
Java : 2007. 1. 17. 12:26
참조 :
public interface List<E> {
void add(E x);
Iterator<E> iterator();
}
public interface Iterator<E> { E next();
boolean hasNext();
}
위와 같이 생겼으며 사용하는 방법은 1. Intoduction에서 보았듯이 List<Integer>처럼 E와 같은 type-parameter 대신에 원하는 실제 타입(actual type parameter)를 넣어 주면 됩니다.
이 때 다음과 같은 코드가 생길 것이라고 생각하는 것은 괜찮지만 사실은 그렇치 않습니다.
public interface IntegerList {
void add(Integer x)
Iterator<Integer> iterator();
}
왜냐면 List<Integer>가 저런 코드를 사용하는 것 처럼 동작하긴 하지만 실제 저런 복사본을 어디에도 만들지 않기 때문입니다. C++의 탬플릿과는 다르다고 합니다.
Agile Java 14장 5절에 보면 C++은 인수화된 형(type-parameter)을 사용할 때마다 새로운 형식을 만든다고 합니다. 하지만 자바는 Erasure(삭제) 방식을 사용한다고 합니다. 즉 인수화된 형식 정보를 지우고 적절한 캐스팅으로 대체 된다고 합니다.
참고 할 곳(물개 선생님께서 정리해 두신 링크들)
- SUN 기본 튜토리얼 : java.sun.com/j2se/1.5/pdf/generics-tutorial.pdf
- 이희승님 정리 문서 : trustin.googlepages.com/Java5Generics.pdf
- Toby님 JavaGeneric과 Erasure : toby.epril.com/?p=248
- advanced: http://blog.interface21.com/main/2007/01/16/a-bridge-too-far/
1. Introduction
자바5.0에 새로 추가된 기능중에 하나로 이게 뭔지 한 문장으로 표현하면 다음과 같습니다.
Generics allow you to abstract over types.
Generic을 사용하는 가장 좋은 예로 Collection을 들 수 있겠습니다.
List myIntList = new LinkedList(); // 1
myIntList.add(new Integer(0)); // 2
Integer x = (Integer) myIntList.iterator().next(); // 3
이러한 기존의 코드를 다음과 같이 사용할 수 있습니다.
List<Integer> myIntList = new LinkedList<Integer>(); // 1’
myIntList.add(new Integer(0)); //2’
Integer x = myIntList.iterator().next(); // 3’
2’ 에서 Integer가 아닌 다른 객체를 넣으려고 하면 컴파일 에러가 발생합니다. 3’ 에서는 캐스팅이 필요없습니다.
기존의 List는 모든 객체가 Object 타입으로 들어가고 그래서 꺼낼 때 캐스팅이 필요했지만 Generic을 사용한 List를 사용하면 type-parameter를 사용해서 특정 타입의 객체만 들어갈 수 있도록 지정할 수 있기 때문에 꺼낼 때 캐스팅이 필요 없습니다.
=> 코드의 가독성과 견고함(robustness)을 높여줍니다.
Generics allow you to abstract over types.
Generic을 사용하는 가장 좋은 예로 Collection을 들 수 있겠습니다.
List myIntList = new LinkedList(); // 1
myIntList.add(new Integer(0)); // 2
Integer x = (Integer) myIntList.iterator().next(); // 3
이러한 기존의 코드를 다음과 같이 사용할 수 있습니다.
List<Integer> myIntList = new LinkedList<Integer>(); // 1’
myIntList.add(new Integer(0)); //2’
Integer x = myIntList.iterator().next(); // 3’
2’ 에서 Integer가 아닌 다른 객체를 넣으려고 하면 컴파일 에러가 발생합니다. 3’ 에서는 캐스팅이 필요없습니다.
기존의 List는 모든 객체가 Object 타입으로 들어가고 그래서 꺼낼 때 캐스팅이 필요했지만 Generic을 사용한 List를 사용하면 type-parameter를 사용해서 특정 타입의 객체만 들어갈 수 있도록 지정할 수 있기 때문에 꺼낼 때 캐스팅이 필요 없습니다.
=> 코드의 가독성과 견고함(robustness)을 높여줍니다.
2. Defining Simple Generics
public interface List<E> {
void add(E x);
Iterator<E> iterator();
}
public interface Iterator<E> { E next();
boolean hasNext();
}
위와 같이 생겼으며 사용하는 방법은 1. Intoduction에서 보았듯이 List<Integer>처럼 E와 같은 type-parameter 대신에 원하는 실제 타입(actual type parameter)를 넣어 주면 됩니다.
이 때 다음과 같은 코드가 생길 것이라고 생각하는 것은 괜찮지만 사실은 그렇치 않습니다.
public interface IntegerList {
void add(Integer x)
Iterator<Integer> iterator();
}
왜냐면 List<Integer>가 저런 코드를 사용하는 것 처럼 동작하긴 하지만 실제 저런 복사본을 어디에도 만들지 않기 때문입니다. C++의 탬플릿과는 다르다고 합니다.
Agile Java 14장 5절에 보면 C++은 인수화된 형(type-parameter)을 사용할 때마다 새로운 형식을 만든다고 합니다. 하지만 자바는 Erasure(삭제) 방식을 사용한다고 합니다. 즉 인수화된 형식 정보를 지우고 적절한 캐스팅으로 대체 된다고 합니다.
3. Generics and Subtyping
List<String> ls = new ArrayList<String>(); //1
List<Object> lo = ls; //2
위 두 줄의 코드 중에서 1번은 문제가 없고 2번은 컴파일 에러가 발생합니다. 다음과 같은 일이 발생 할 수 있기 때문에 미연에 방지하기 위함입니다.
lo.add(new Object()); // 3
String s = ls.get(0); // 4: attempts to assign an Object to a String!
List<Object> lo = ls; //2
위 두 줄의 코드 중에서 1번은 문제가 없고 2번은 컴파일 에러가 발생합니다. 다음과 같은 일이 발생 할 수 있기 때문에 미연에 방지하기 위함입니다.
lo.add(new Object()); // 3
String s = ls.get(0); // 4: attempts to assign an Object to a String!
4. Wildcards
void printCollection(Collection c) {
Iterator i = c.iterator();
for (k = 0; k < c.size(); k++) {
System.out.println(i.next());
}}
위와 같은 코드를 Generic을 사용하여 다음과 같이 표현하고 싶겠지만...
void printCollection(Collection<Object> c) {
for (Object e : c) {
System.out.println(e);
}}
원하는 대로 동작하지 않을 것입니다. 오로지 Object만 들어있는 컬렉션을 받을 수 있을 뿐... List<String>을 printCollection의 매개 변수로 넣으려고 하면 컴파일 에러가 발생합니다. 3. Generics and Subtyping에서 발생했던 문제와 같은 논리입니다.
Collection<Object>는 모든 타입들의 Supertype이 아닙니다. 모든 타입들의 Supertyle은 ? 와일드 카드를 사용해서 표현합니다. 즉 Collection<?> 이렇게 표현하면 됩니다. 따라서 위의 메소드를 다음과 같이 작성하면 원하는 결과를 얻을 수 있습니다.
void printCollection(Collection<?> c) {
for (Object e : c) {
System.out.println(e);
}}
주의할 것은 ? 을 사용한 콜렉션에 추가하는 일을 하면 컴파일 에러가 발생한다는 것입니다.
Collection<?> c = new ArrayList<String>();
c.add(new Object()); // compile time error
왜냐면.. ? 는 모든 타입을 나타내기 때문에 Collection에 어떤 타입이 들어갈지 예측할 수 없기 때문입니다. 1. Introduction에서 Collection에 타입의 제약을 가해서 빼낼 때 캐스팅 없이 알고 있는 타입으로 빼내는 견고함과 가독성을 위해 Generics를 사용하기 시작한 것을 기억하실 것입니다. 그렇다면 지금 벌어지는 일이 그와는 정반대 되는 일임을 알 수 있을 것입니다. 따라서 컴파일 에러가 발생합니다.
넣는 것과 다르게 ? 를 사용한 Collection에서 꺼내는 일(get())은 가능합니다. 위와 같은 코드의 경우 나오는 객체의 타입을 Object 타입으로 예상하고 있고 실제 모든 객체들은 Object 타입이기 때문입니다.
? 의 기본 상위 경계는 Object 이기 때문이라고 해도 되겠네요. 조금 후에 extends를 사용해서 상위 경계를 원하는 타입으로 제한할 수 있는데 그때는 나오는 타입을 역시 원하는 상위 타입으로 예측할 수 있기 때문에 get()을 하는데는 아무 문제가 없습니다.
위와 같은 구조에서 Canvas의 drawAll의 코드가 다음과 같다고 생각해 봅니다.
public void drawAll(List<Shape> shapes) {
for (Shape s: shapes) {
s.draw(this);
}
}
이 때 drawAll의 매개 변수로 넘겨줄 수 있는 Collection은 오로지 List<Shape> 밖에 없습니다. 하지만 메소드 선언 부를 다음과 같이 바꾸면 public void drawAll(List<? extends Shape> shapes) { ... } List<Circle>과 List<Rectangle>도 매개변수로 넘겨 줄 수 있습니다.
<? extends Shape>와 같은 표현을 상위 경계을 가했다고 해서 1bounded wildcard 라고 하며 Shape를 상위 경계(upper bound)라고 합니다.
이때도 역시 4. Wildcards 에서 보았던 것처럼 다음의 코드는 컴파일 에러를 발생합니다.
public void addRectangle(List<? extends Shape> shapes) {
shapes.add(0, new Rectangle()); // compile-time error!
}
? 의 상위 제한은 알지만 여전히 어떤 객체가 들어갈지는 예측할 수가 없습니다. List<Rectangle> 에 Rectangle의 상위 타입(Shape의 하위타입이면서..)이 들어 갈 수도 있기 때문입니다.
Iterator i = c.iterator();
for (k = 0; k < c.size(); k++) {
System.out.println(i.next());
}}
위와 같은 코드를 Generic을 사용하여 다음과 같이 표현하고 싶겠지만...
void printCollection(Collection<Object> c) {
for (Object e : c) {
System.out.println(e);
}}
원하는 대로 동작하지 않을 것입니다. 오로지 Object만 들어있는 컬렉션을 받을 수 있을 뿐... List<String>을 printCollection의 매개 변수로 넣으려고 하면 컴파일 에러가 발생합니다. 3. Generics and Subtyping에서 발생했던 문제와 같은 논리입니다.
Collection<Object>는 모든 타입들의 Supertype이 아닙니다. 모든 타입들의 Supertyle은 ? 와일드 카드를 사용해서 표현합니다. 즉 Collection<?> 이렇게 표현하면 됩니다. 따라서 위의 메소드를 다음과 같이 작성하면 원하는 결과를 얻을 수 있습니다.
void printCollection(Collection<?> c) {
for (Object e : c) {
System.out.println(e);
}}
주의할 것은 ? 을 사용한 콜렉션에 추가하는 일을 하면 컴파일 에러가 발생한다는 것입니다.
Collection<?> c = new ArrayList<String>();
c.add(new Object()); // compile time error
왜냐면.. ? 는 모든 타입을 나타내기 때문에 Collection에 어떤 타입이 들어갈지 예측할 수 없기 때문입니다. 1. Introduction에서 Collection에 타입의 제약을 가해서 빼낼 때 캐스팅 없이 알고 있는 타입으로 빼내는 견고함과 가독성을 위해 Generics를 사용하기 시작한 것을 기억하실 것입니다. 그렇다면 지금 벌어지는 일이 그와는 정반대 되는 일임을 알 수 있을 것입니다. 따라서 컴파일 에러가 발생합니다.
넣는 것과 다르게 ? 를 사용한 Collection에서 꺼내는 일(get())은 가능합니다. 위와 같은 코드의 경우 나오는 객체의 타입을 Object 타입으로 예상하고 있고 실제 모든 객체들은 Object 타입이기 때문입니다.
? 의 기본 상위 경계는 Object 이기 때문이라고 해도 되겠네요. 조금 후에 extends를 사용해서 상위 경계를 원하는 타입으로 제한할 수 있는데 그때는 나오는 타입을 역시 원하는 상위 타입으로 예측할 수 있기 때문에 get()을 하는데는 아무 문제가 없습니다.
4.1 Bounded Wildcards
public void drawAll(List<Shape> shapes) {
for (Shape s: shapes) {
s.draw(this);
}
}
이 때 drawAll의 매개 변수로 넘겨줄 수 있는 Collection은 오로지 List<Shape> 밖에 없습니다. 하지만 메소드 선언 부를 다음과 같이 바꾸면 public void drawAll(List<? extends Shape> shapes) { ... } List<Circle>과 List<Rectangle>도 매개변수로 넘겨 줄 수 있습니다.
<? extends Shape>와 같은 표현을 상위 경계을 가했다고 해서 1bounded wildcard 라고 하며 Shape를 상위 경계(upper bound)라고 합니다.
이때도 역시 4. Wildcards 에서 보았던 것처럼 다음의 코드는 컴파일 에러를 발생합니다.
public void addRectangle(List<? extends Shape> shapes) {
shapes.add(0, new Rectangle()); // compile-time error!
}
? 의 상위 제한은 알지만 여전히 어떤 객체가 들어갈지는 예측할 수가 없습니다. List<Rectangle> 에 Rectangle의 상위 타입(Shape의 하위타입이면서..)이 들어 갈 수도 있기 때문입니다.
5 Generic Methods
static void fromArrayToCollection(Object[] a, Collection<?> c) {
for (Object o : a) {
c.add(o); // compile time error
}}
배열에 있는 요소들을 콜렉션으로 옮겨 담는 메소드인데 위의 메소드에서 컴파일 에러가 발생하는 이유는 앞에서도 설명을 했지만 ? 와일드 카드는 모든 타입을 나타내기 때문에 Collection이 어느 특정 타입을 유지 하리라 예측 할 수 없기 때문입니다.
static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
for (T o : a) {
c.add(o); // correct
}}
하지만 위 코드는 에러가 발생하지 않습니다. T라는 특정 타입 하나로 제한되었기 때문입니다. 이런 메소드를 generic method라고 합니다. 위 메소드를 사요하면 특정 타입만을 가지는 Collection을 만들 수 있기 때문에 Generics를 사용하는 이유인 견고함과 가독성 유지에 위배되지 않습니다.
그렇다면 문제는 언제 ? 를 쓰고 언제 generic method를 사용하는가 입니다.
interface Collection<E> {
public boolean containsAll(Collection<?> c);
public boolean addAll(Collection<? extends E> c);
}
위 코드를 아래 처럼 generic method를 사용해서 표현할 수도 있습니다.
interface Collection<E> {
public <T> boolean containsAll(Collection<T> c);
public <T extends E> boolean addAll(Collection<T> c);
// hey, type variables can have bounds too!
}
하지면 여기서 사용된 T라는 type parameter는 리턴타입에 영향을 주지도 않으며 매개변수들 간의 계층 관계를 나타내지도 않습니다. 이런 경우에서 addAll에서의 E는 상위제한을 가하는 용도로만 사용되었습니다. 또한 다양한 type을 받아 들이기 위해 T라는 type parameter를 사용했습니다. 이럴때는 ? 와일드카드를 사용하는 것이 좋습니다. 2
class Collections {
public static <T> void copy(List<T> dest, List<? extends T> src){...}
}
아래와 같이 S라는 type parameter를 상용할 수도 있습니다.
class Collections {
public static <T, S extends T> void copy(List<T> dest, List<S> src){...}
}
하지만 여기서 매개변수들 만 본다면 T라는 type parameter는 두 개의 매개변수에서 모두 사용된 것이지만 S는 하나의 매개변수에서 사용된 것이나 마찬가지 입니다. 따라서 이 경우도 ? 와일드 카드를 사용하는 것이 보다 깨끗하고 간결합니다. 3
for (Object o : a) {
c.add(o); // compile time error
}}
배열에 있는 요소들을 콜렉션으로 옮겨 담는 메소드인데 위의 메소드에서 컴파일 에러가 발생하는 이유는 앞에서도 설명을 했지만 ? 와일드 카드는 모든 타입을 나타내기 때문에 Collection이 어느 특정 타입을 유지 하리라 예측 할 수 없기 때문입니다.
static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
for (T o : a) {
c.add(o); // correct
}}
하지만 위 코드는 에러가 발생하지 않습니다. T라는 특정 타입 하나로 제한되었기 때문입니다. 이런 메소드를 generic method라고 합니다. 위 메소드를 사요하면 특정 타입만을 가지는 Collection을 만들 수 있기 때문에 Generics를 사용하는 이유인 견고함과 가독성 유지에 위배되지 않습니다.
그렇다면 문제는 언제 ? 를 쓰고 언제 generic method를 사용하는가 입니다.
interface Collection<E> {
public boolean containsAll(Collection<?> c);
public boolean addAll(Collection<? extends E> c);
}
위 코드를 아래 처럼 generic method를 사용해서 표현할 수도 있습니다.
interface Collection<E> {
public <T> boolean containsAll(Collection<T> c);
public <T extends E> boolean addAll(Collection<T> c);
// hey, type variables can have bounds too!
}
하지면 여기서 사용된 T라는 type parameter는 리턴타입에 영향을 주지도 않으며 매개변수들 간의 계층 관계를 나타내지도 않습니다. 이런 경우에서 addAll에서의 E는 상위제한을 가하는 용도로만 사용되었습니다. 또한 다양한 type을 받아 들이기 위해 T라는 type parameter를 사용했습니다. 이럴때는 ? 와일드카드를 사용하는 것이 좋습니다. 2
- 다형성을 나타내고 싶고 매개 변수에서 한번만 사용되는 type parameter는 generic method보다 ? 와일드 카들르 사용하는 것이 좋습니다.
- generic method는 매개변수 타입들 간 or/and 리턴 타입 간의 관계를 표현할 때 사용하는 것이 좋습니다.
class Collections {
public static <T> void copy(List<T> dest, List<? extends T> src){...}
}
아래와 같이 S라는 type parameter를 상용할 수도 있습니다.
class Collections {
public static <T, S extends T> void copy(List<T> dest, List<S> src){...}
}
하지만 여기서 매개변수들 만 본다면 T라는 type parameter는 두 개의 매개변수에서 모두 사용된 것이지만 S는 하나의 매개변수에서 사용된 것이나 마찬가지 입니다. 따라서 이 경우도 ? 와일드 카드를 사용하는 것이 보다 깨끗하고 간결합니다. 3
참고 할 곳(물개 선생님께서 정리해 두신 링크들)
- SUN 기본 튜토리얼 : java.sun.com/j2se/1.5/pdf/generics-tutorial.pdf
- 이희승님 정리 문서 : trustin.googlepages.com/Java5Generics.pdf
- Toby님 JavaGeneric과 Erasure : toby.epril.com/?p=248
- advanced: http://blog.interface21.com/main/2007/01/16/a-bridge-too-far/
'Java' 카테고리의 다른 글
Stream 인코딩 바꾸기 (0) | 2007.02.28 |
---|---|
SWT 프로그램 실행하기 (0) | 2007.02.21 |
제8회 한국 자바 개발자 컨퍼런스 (2) | 2007.01.25 |
GC관련 아티클 (0) | 2007.01.19 |
Generics 번외 - 겉모습만 보곤 알 수 없슴. (2) | 2007.01.17 |
Generics (2) | 2007.01.17 |
Eclipse 단축키 모음 (6) | 2007.01.11 |
Generic과 다형성 2탄 (4) | 2007.01.05 |
Generic과 다형성 (0) | 2007.01.05 |
자바 검은 띠에 도전해 보시길~ (2) | 2006.12.31 |
Hiding Method (0) | 2006.12.31 |
TAG generics
오홋.. 너무 멋지다. 어제 오후에 자료를 전해 드렸는데, 오늘 아침에 정리되어 있다니. 흠.. 최상의 공부파트너를 만난 게로군요. 제게도 좋은 자극이 됩니다. 화이팅~~
넵 ^^ 감사합니다.
좋은 자료를 주셔서 도움이 많이 됐습니다.
5장 뒷 부분은 이해가 덜 되서요~ 이따 학교가서 더 읽어봐야겠어요.