Java Generic 02 : Generics Basic

from : https://docs.oracle.com/javase/tutorial/extra/generics/subtype.html

Generics and Subtyping : 

List<String> ls = new ArrayList<String>();
List<Object> lo = ls;
    - 첫번째 라인은 적법한 코드이다.
    - 두번째 코드는 ? 간단히 봐서는 적법한 코드로 보임
결과는 컴파일 타임 오류가 발생한다. (변환 오류 발생)

Generic에서는 Foo가 Bar의 서브 타입이라고 가정하고 G가 Generic타입으로 선언된경우, G<Foo>는 G<Bar>의 서브 타입으로 보지 않는다.
이러한 사실은 Generics를 배우기 어렵게 하는 원인이다. (이는 우리가 단순히 직관적으로 생각하는 것과 거리가 멀기 때문이다.)

예를 들어 자동차 매장에서 드라이버의 리스트를 인구조사국에 보낸다고 가정하자, 이것은 합당해 보인다.

우리는 List<Driver>는 List<Person>이라고 생각한다. 왜냐하면 Person의 서브 타입이 Driver이기 때문이다. 사실 driver의 registry의 값을 복사해서 전송하면 된다.
반면에 인구조사국에서는 새로운 Person을 전달받은 리스트에 넣을수 없다. 왜냐하면 그 사람은 Driver가 아니기 때문이다.

Wildcards : 
컬렉션의 모든 엘리먼트를 출력하는 루틴을 작성한다고 생각해보자.

5.0 이전 버젼의 코드 :
void printCollection(Collection c) {
    Iterator i = c.iterator();
    for ( k = 0; k < c.size(); k++) {
        System.out.println(i.next());
    }
}
단순한 generics를 이용하여 작성한 코드 :
void printCollection(Collection<Object> c) {
    for (Object e : c) {
        System.out.println(e);
    }
}
두 코드의 비교 :
과거 버젼의 코드가 새로운 generics를 이용한 코드보더 더 유용하다.
    이는 과거 버젼은 어떠한 파라미터에 대한 컬렉션도 모두 수용이 가능한 반면, 새로운 코드는 오직 Collection<Object> 만 받을 수 있다. 이는 이전 설명에서 Collection<Object> 가 모든 컬렉션의 수퍼타입이 아니기 때문이다.

모든 컬렉션의 수퍼타입으로 만든 코드 :
void printCollection(Collection<?> c) {
    for (Object e : c) {
        System.out.println(e);
    }
}
Collection<?> 은 "collection of unknown"이라고 하며, 파라미터로 어떠한 엘리먼트가 와도 매칭이 된다. 이를 wildcard type라고 부른다.

- wildcard type 어떠한 컬렉션의 타입이라도 수용이 가능하다.
- 이 코드는 항상 safe하다. 어떠한 컬렉션 객체도 담을 수 있기 때문이다.

Collection<?> c = new ArrayList<String>();
c.add(new Object()); // 컴파일 타임 에러
c.add(null); // 가능 

- 이 코드는 c를 보고서는 어떠한 엘리먼트 타입을 의도했는지 알지 못한다. 우리는 object를 이 타입에 추가할 수 없다. (컴파일 타임 에러)
- add() 메소드는 아규먼트 타입 E값을 받아들이도록 되었다.
- 실제 파라미터 타입이 ? 로 설정하면, 이것은 알려지지 않은 타입으로 설정된다.
- 이 코드는 어떠한 타입이 들어가야할지 정의하지 못하기 때문에 어떠한 값도 들어갈 수 없게 된다. 유일하게 null만 예외적으로 들어갈 수 있다.

반면 List<?>에서 우리는 get()을 이용할 수 있다. result type은 unknown타입이다. 그러나 우리는 그것이 항상 object라는 것을 알 수 있다. 그러므로 get()결과에 다양한 타입을 설정하는 것에는 안전하다. 혹은 예상되는 Object 타입을 파라미터로 전달 할 수 있다.

Bounded Wikdcards 
간단한 shapes를 그리는 어플리케이션을 생각해보자. 이는 rectangles와 circles를 그린다. 이 shapes를 그리는 문제에서 클래스 계층을 다음과 같이 그릴 수 있다.
public abstract class Shape {
    public abstract void draw(Canvas c);
}
public class Circle extends Shape {
    private int x, y, radius;
    private void draw(Canvas c) {
        ...
    }
}
public class Rectangle extends Shape {
    private int x, y, width, height;
    public void draw(Canvas a) {
        ...
    }
}

아래 클래스는 canvas에 그림을 그리도록 한다.
public class Canvas {
    public void draw(Shape s) {
        s.draw(this);
    }
}
다음은 shapes들을 그려주는 메소드이다. 그것들은 list에 존재하고 있고, 그것의 전체를 그려주는 메소드가 다음과 같다.
public void drawAll(List<Shape shapes) {
    for (Shape s : shapes) {
        s.draw(this);
    }
}

이 코드는 오직 Shape의 리스트만을 출력할 수 있다. 이것은 List<Circle>를 처리하지 못한다.
이를 해결하기 위해서 다음과 같이 메소드를 작성하면 해결이 된다.

public void drawAll(List<? extends Shape> shapes) {
    ...
}
이제 우리는 Shape의 자식 클래스의 리스트를 처리할 수 있게 되었다. 이렇게 작성함으로 해서 List<Circle>를 처리할 수 있으며 우리가 원하는 것이다.

List<? extends Shape> 이것이 bounded wildcard의 예제이다. '?'는 unknown타입을 말한다. 여기서는 이러한 unknown타입이 Shape를 extends 했다는 것을 알려 주는 것이다.
이 코드에서 Shape를 upper bound of the wildcard라고 부른다.


wildchard의 제약 :

wildcard를 이용하여 동적인 처리를 위해서는 비용이 따른다. 이 비용은 메소드의 바디에서 shapes에 쓰기는 금지되게 된다. 예를 들면 다음과 같다.
public void addRectangle(List<? extends Shape> shapes) {
    // 컴파일 에러
    shapes.add(0, new Rectangle);
}
이렇게 쓰기가 금지된 이유는 무엇일까? shapes.add()의 타입은 "? extends Shape" 이다. 즉, 우리는 shapes의 타입이 정확이 무엇인지 규정하지 못한다. 이것이 정확히 Rectangle의 슈퍼타입인지 규정할 수 없기 때문이다. 아마도 shapes.add()의 타입이 Rectangle의 슈퍼타입일수도 있고, 아닐수도 있기 때문에 Rectangle를 add하는 것은 type safe하지 못하게 된다.











Share this

Related Posts

Previous
Next Post »