Tag: java

Java generic in return context

Posted by – 2009-09-25

아래 코드를 보시고 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. =(

Access level of methods in package-private class

Posted by – 2009-09-22

Java에서 (C++도 마찬가지지만) 권장되는 access level 사용법은 다음과 같습니다.

Use the most restrictive access level that makes sense for a particular member.

쉽게 얘기해서 private으로 충분하면 private을 쓰고 다음으로 package-private, protected, public 순으로 쓰라는 얘기.

그런데 C++와는 달리 Java는 class에도 public과 package-private 두 종류의 access level이 있습니다. 이 중 public class의 method들은 위의 권장 사항을 따르면 될 듯 한데 package-private class일 경우는 어떨까요?

Package-private class에서는 실제 사용될 수 있는 조건으로 봤을 때 method들이 public이건 protected건 모두 package-private과 같은 조건을 가집니다. 즉, 아무리 method의 access level을 public이나 protected로 해봐야 다른 package에선 이 method를 사용할 수가 없습니다. 따라서 the most restrictive한 level을 사용하라는 위 권장 사항을 따르자면 package-private class의 method들은private이 아니라면 모두 package-private access level을 사용해야 합니다.

그런데 코딩을 하다보니 이 방법엔 한가지 문제가 있더군요. 가끔 package-private class를 public class로 만들어야 할 경우들이 생기는데 이때 method들이 모두 package-private이면 다른 package에서 사용할 수가 없습니다. 그래서 다시 method들의 access level을 조정해야 할 필요가 생기는데 이 시점은 이미 코드를 작성했을 때와 멀어서 자칫 잘못된 access level을 지정할 수 있게 됩니다.

그래서 생각한건데... package-private class의 method들을 만들 때 access level은 이 class의 access level을 public으로 가정하고 작성하는 것이 어떨까요?

Checked exception, 이거 써도 되나?

Posted by – 2009-09-15

mkseo님 블로그Best Practices for Exception Handling에 관한 글이 올라왔는데 댓글을 읽고 쓰고 하다 궁금한게 생겼습니다.

Checked exception, 이거 써도 되나?

mkseo님이 링크를 걸어준 The Trouble with Checked ExceptionsDoes Java need Checked Exceptions?, Java's checked exceptions were a mistake (and here's what I would like to do about it)등의 글을 읽다 보니 현재까지의 결론은 "java의 checked exception실험은 실패다"정도인 것 같더군요.

몇가지 이유로는

  1. 원래 language designer의 의도와는 달리 많은 exception이 무시되고 있다. 원래 의도는 compile-time에 에러를 냄으로써 개발자들에게 이 예외를 지금 처리하거나 아니면 위로 전달해야 한다는 메시지를 보내는 것이었다. 하지만 예외를 전달하기 위해서는 method의 signature를 수정해야 한다. 개발자들로서는 이 상황을 벗어날 수 있는 가장 쉬운 방법을 찾게 되고, 이게 바로 catch 후 무시하는 것인데 대부분의 코드가 이런 식으로 작성되고 있다. catch (...) {}
  2. 원래 exception의 목적중 하나는 예외가 발생한 지점과 처리할 지점의 분리이다. 즉 low-level에서 발생했지만 거기서 처리가 불가능한 exception들을 위로 전달하여 처리하게 한다... 인데... checked exception은 그 사이에 있는 모든 코드가 이 exception에 대해 알아야만 하게 강제한다. 즉, 전달되는 과정이 투명하지 않다.
  3. Versionability - 어느 method가 A, B라는 checked exception만을 던진다고 선언되었다면 client code들은 이 예외만을 처리하거나 위로 전달하도록 작성된다. 나중에 이 method의 다음 버전에서 C라는 exception을 던질 필요가 생기면 client와의 contract이 깨지게 된다.
  4. Scalability - A, B, C, D라는 method들이 각각 4개씩의 checked exception을 던지고 U라는 method가 이 것들을 호출하는데 예외들을 처리하지 않는다면 U는 총 16개의 checked exception을 던진다고 선언해야 한다. 상위로 올라갈 수록 선언해야 할 exception의 개수는 기하급수적으로 늘어나게 된다.

등이 있습니다. 4번의 경우에는 각 레벨에 맞는 exception을 정의해서 사용하면 해결할 수는 있을 것 같습니다. 즉, A, B, C, D가 던지는 16개의 exception을 U가 던질 때는 자기의 context에 맞는 예외 두어개로 mapping하여 던진다는 식이지요.

암튼.. 그럼 왜 여지껏 저는 checked exception이 매우 그럴 듯해 보였던 걸까요? C#의 한 language designer가 이렇게 얘기했다네요.

Examination of small programs leads to the conclusion that requiring exception specifications could both enhance developer productivity and enhance code quality, but experience with large software projects suggests a different result -- decreased productivity and little or no increase in code quality.

하지만 몇가지 경우엔 checked exception이 유용할 수 있다고 합니다.

  • 예외가 significant boundary를 넘어가는 경우. 예를 들어 CORBA의 machine boundary같은... 이런 경우 좀 더 명시적인 예외 스펙이 도움이 된다.
  • package 내부에서 사용되나 절대 외부로 넘어가서는 안되는 예외의 경우 checked exception을 사용하면 컴파일러가 이를 감시하게 할 수 있다.

예외에 관해 참고할 만한 글들입니다.