'제네릭'에 해당되는 글 3건

  1. 2008/07/20 자바 제네릭 2
  2. 2008/07/20 자바 제네릭
  3. 2008/04/11 간략한 배열 초기화

자바 제네릭 2

자바/제네릭(Generic) 2008/07/20 03:30 posted by 낭만검객
pushAll에 이어 popAll을 추가 구현해 보겠습니다. 단순하게 제네릭을 이용하면 아마도 다음 코드와 비슷할 것입니다.

public void popAll(Collection<E> dst) {
  while(!isEmpty())
    dst.add(pop());
}

그리고 popAll을 다음처럼 호출할 것입니다.

Stack<Number> numberStack = new Stack<Number>();
Collection<Object> objects = ...;
numberStack.popAll(objects);

이 코드 역시 컴파일 할 수 없습니다. Collection<Object>와 Collection<Number>는 다릅니다.

앞에 코드는 Object 형 묶음인 objects변수에  Number형을 싹 꺼내서 담으려고 합니다. 이 경우는 앞 글에서 언급한 <? extends E>로 해결할 수 없습니다. 'Object extends Number'와 같은 의미구문은 자바세상에서 절대 존재할 수 없습니다. 이 경우는 역으로 <E extends ?> 가 맞습니다. 그렇다고 그렇게 코드를 작성하지는 못하고, super라는 키워드를 이용합니다. <? super E>

dst변수는 데이터를 이용하는 소비자(consumer)입니다. 제 관찰 기준으로, 매개변수의 메소드에 인자를 넘겨 호출하면 소비자입니다. (혹시 이 기준이 틀리다면 댓글 ^^)

이제 제네릭을 이용할 때, 인자에 단순히 E라고 적을 것이 아니라 상황에 따라 extends, 혹은 super를 적어야 한다는 것을 아셨습니다. 조슈아 아저씨가 쉽게 외우도록 한국형 공식을 만들었습니다.

PECS

약어입니다. producer-extends, consumer-super.

JavaOne2008에서 조슈아 아저씨는 작년에 이 부분 설명이 좀 어려웠다고 고백하며 미안하다 했습니다. 아무래도 어느 정도의 암기가 필요했다고 생각한 것 같습니다. 저도 한 번 설명듣고 PECS를 외우고 나니, 다른 보기를 들 때 이해하는데 한결 나았습니다. 참고로 <Effective Java, 2nd> 책에도 크게 약어를 적어 놓았습니다.

PECS stands for producer-extends, consumer-super.

주의할 점이 있습니다.

한 변수가 P와 C의 역할을 다 하는 경우가 있습니다. 그 때는 그냥 E만 적습니다. (제네릭 타입)

두 번째로 메소드를 호출하는 쪽에서는, 구현 코드에서 extends를 썼는지 super를 썼는지 의식하지 않도록 메소드를 디자인해야합니다. 앞 두 글의 클라이언트 코드는 구현 쪽을 신경쓰지 않았습니다. 메소드 구현부에서 클라이언트의 의도대로 수행되도록 적절히 extends나 super 키워드를 적었습니다.

자바 제네릭

자바/제네릭(Generic) 2008/07/20 02:35 posted by 낭만검객
먼저, 아래 내용은 <Effective Java, 2nd> Item28의 일부를 정리한 것임을 밝힙니다.

--- 아래 ---

어떠한 타입도 담을 수 있는 Stack 클래스가 있습니다.

public class Stack<E> {
  public Stack();
  public void push(E e);
  public E pop();
  public boolean isEmpty();
}

pushAll메소드를 구현하여 덧붙이면 다음과 같이 작성할 수 있습니다.

public void pushAll(Iterable<E> src) {
  for (E e : src)
    push(e);
}

이렇게 작성한 메소드를 다음처럼 사용할 수도 있습니다.

Stack<Number> numberStack = new Stack<Number>();
Iterable<Integer> integers = ...;
numberStack.pushAll(integers);

사실 이 코드는 컴파일조차 할 수 없습니다.

Integer가 Number를 상속했기에 당연히 되리라 생각할 수 있지만, 컴파일 타임에 Iterable<Integer>와 Iterable<Number>는 완전히 다른 타입입니다. 제네릭에서 가장 착각하기 쉬운 부분입니다.

이런 경우에는 'bounded wildcard type'을 이용하면 '상식대로' 돌아가도록 개선할 수 있습니다.

public void pushAll(Iterable<? extends E> src) {
  for (E e : src)
    push(e);
}

'Iterable<? extends E>'는 'E의 하위타입에 대한 Iterable' 이라고 읽습니다. 그렇다고 E를 못넘기는 것은 아닙니다. E와 E의 하위타입 모두 가능합니다.

참고로, 여기서 변수 src는 정보를 생산(e)하고 있지, 다른 변수 값을 이용하지 않습니다. 이 경우 src를 생산자(producer)라고 부를 수 있습니다. 이런 변수를 'producer'라고 부르는 것은 <Effective Java>의 지은이 'Joshua Bloch'가 임의로 붙인 것 같습니다. 참고라고 적었지만 일단 외워두는 것이 좋습니다. (이유는 다음 글에)

다음 글에서는 반대 경우를 설명 드리겠습니다.

간략한 배열 초기화

자바 2008/04/11 11:28 posted by 낭만검객
http://stuffthathappens.com/blog/2008/04/04/simplified-array-syntax/ 블로그에서 재밌는 내용을 발견해서 소개합니다.

객체 배열을 초기화할 때 대개 다음과 같은 형식으로 작성합니다.

String[] names = new String[] { "cybaek", "milk012", "cymilk012"};
하지만 Java5를 사용한다면, 가변길이 인자, 정적 메소드 임포트, 제네릭 기능을 이용하여 다음과 같이 색다르게 기술할 수 있습니다. (글자수는 확 늘었군요 -_-)

String[] names = LangUtils.array("cybaek", "milk012", "cymilk012");

public class LangUtils {
  public static <T> T[] array(T... t) {
    return t;
  }
}
이번에는 LangUtils.array() 부분의 글자수를 더 줄여보겠습니다.

import static LangUtils.array;
String[] names = array("cybaek", "milk012", "cymilk012");
import static 구문으로 메소드를 선언하면 해당 메소드 이름만 적어도 사용할 수 있습니다. 이런 스타일은 JUnit4 테스트케이스에서 많이 씁니다. 이젠 간단한게 array라는 메소드 이름만으로 손쉽게 객체 배열을 만들 수 있습니다.

하지만 이 방법은 '객체'만 배열로 만든다는 단점이 있습니다. 다음과 같은 코드는 동작하지 않습니다.

int[] ids = array(1, 2, 3);
박싱/언박싱 기능 덕에 다음 코드는 동작합니다.

Integer[] ids = array(1, 2, 3);
int 배열을 반환하려면 원시타입을 위한 별도 메소드를 만들 수 밖에 없습니다. 하지만 원시 타입이 몇 개 되지 않으니 충분히 할 만합니다.