Java generic in return context

아래 코드를 보시고 1, 2번 라인중에 어디서 에러가 날 지를 찾아보세요.
interface B {
void doB();
}
class D implements B {
public void doB() {}
}
interface H {
B getB();
}
class HImpl implements H {
public B getB() {
return new D();  // 1)
}
}
...
H h = new HImpl();
D d = h.getB(); // 2)

네, 2번 라인입니다.

그럼 다음 코드는?

interface B1 {
void doB1();
}
interface B2 {
void doB2();
}
class D implements B1, B2 {
public void doB1() {}
public void doB2() {}
}
interface H {
<T extends B1 & B2> T getB();
}
class HImpl implements H {
public <T extends B1 & B2> T getB() {
return new D();  // 1)
}
}
...
H h = new HImpl();
D d = h.getB(); // 2)

네, 1번입니다. 잘 이해가 되질 않아 사내 메일링 리스트에 물어보니
I think you're seeing and interpreting it as "this method can return anything that implements both interfaces". What it's actually saying is "the caller is going to tell you a specific class that implements both interfaces, and you must return one of those".

이랍니다. 즉 T 타입이 아직 결정되지 않은 상태라 D와 T는 compatible한 타입이 아닌거죠.

강제로 casting을 해서 컴파일이 되게 만들면 그 코드는 runtime error (ClassCastException)를 발생하게 됩니다. 다음 코드를 보세요. 즉 이런 방법으론 도저히 type-safe한 코드를 작성할 수 없습니다.

class D2 implements B1, B2 {
public void doB1() {}
public void doB2() {}
}
...
D2 d = h.getB(); // ClassCastException!!

이 같은 상황을 방지하기 위해 1번 라인에서 에러를 내 주는 것이죠.

코딩 가이드라인을 만들어 보자면 "method의 parameter로 사용되지 않는 type variable을 사용하면 안된다"입니다. 다음과 같은 경우는 parameter로도 사용되고 있으므로 문제가  없는 경우들입니다.

<T> T makeInstance(Class<T> cls) { return cls.newInstance(); }
...
<T extends Comparable<? super T>> T min(T a, T b) {
return a.compareTo(b) < 1 ? a : b;
}
위의 예제 코드같은 경우 아래와 같은 방법으로 type-safe하게 작성될 수 있습니다.
interface B1 {
void doB1();
}
interface B2 {
void doB2();
}
class D implements B1, B2 {
public void doB1() {}
public void doB2() {}
}
interface H  <T extends B1, B2> {
T getB();
}
class HImpl implements H<D> {
public D getB() {
return new D();  // 1)
}
}
...
H<D> h = new HImpl();
D d = h.getB(); // 2)

사실... 아직도 잘 모르겠어요. Java generic. =(

Comments

  1. "method의 parameter로 사용되지 않는 type variable을 사용하면 안된다" 에 대한 counter example을 보여드리면 ^^

    mkseo@ubuntu:~/tmp$ javac -1.5 Generics.java
    mkseo@ubuntu:~/tmp$ java Generics
    1
    2
    3
    mkseo@ubuntu:~/tmp$ cat Generics.java
    import java.util.*;

    class Lists {
    public static ArrayList newArrayList() {
    return new ArrayList();
    }
    }

    public class Generics {
    public static void main(String[] args) {
    List li = Lists.newArrayList();
    li.add(1);
    li.add(2);
    li.add(3);
    for (int i: li) {
    System.out.println(i);
    }
    }
    }
    mkseo@ubuntu:~/tmp$

    제가 보기에 위에서 하신 부분에서의 에러는 타입 T가 쓰인적이 없기때문에 타입 T가 정의되지 않아서인걸로 생각됩니다...

    http://mkseo.pe.kr/blog/?p=1691 에 링크된 자료 읽어보세요. 그리고 http://www.angelikalanger.com/GenericsFAQ/FAQSections/TechnicalDetails.html#Topic3 도요.

    전 자바 Generics는 정말 최대의 실패작이 아닌가 하는 생각을 합니다..

    ReplyDelete
  2. 꺽쇠가 다 날아가네요. 다시...
    mkseo@ubuntu:~/tmp$ cat Generics.java
    import java.util.*;

    class Lists {
    public static <T> ArrayList<T> newArrayList() {
    return new ArrayList<T>();
    }
    }

    public class Generics {
    public static void main(String[] args) {
    List<Integer> li = Lists.newArrayList();
    li.add(1);
    li.add(2);
    li.add(3);
    for (int i: li) {
    System.out.println(i);
    }
    }
    }
    mkseo@ubuntu:~/tmp$

    참 이 코드의 원본은 http://code.google.com/p/google-collections/source/browse/trunk/src/com/google/common/collect/Lists.java#62 에 있습니다.

    ReplyDelete
  3. 흠.. 근데 newArrayList의 return type은 T가 아니라 ArrayList라 좀 다른 경우 아닐까요?

    ReplyDelete
  4. 아.. 그럼 method의 parameter로 사용되지 않는 type variable을 리턴타입으로 사용하면 안된다 이렇게 되겠네요.

    ReplyDelete
  5. Checked exception도 싫어하시고 generic도... :)
    그럼 java의 productivity는 어디서 나오는걸까요? 역시 eclipse? ^^;

    ReplyDelete
  6. 네. 아마 eclipse!! ^^ eclipse처럼 척척 개발되는 환경은 다른 언어는 힘든거 같아요. 파이썬도 C++도 루비도 다 엉망이고...

    자바빼면 C#이 참으로 괜찮은 언어이고 .NET이 참으로 괜찮은 프레임워크라고 생각합니다... 근데 참 C#은 도무지 뜨지를 못하는거 같아요.

    ReplyDelete
  7. 예전 경험에 비춰보면 ms기술들은 너무 닫혀 있는 거 같아요. 그래서 Windows 플랫폼 아닌데선 뜨기 쉽지 않은 듯.

    ReplyDelete

Post a Comment

Popular Posts