Whiteship's Note

'effective java'에 해당되는 글 19건

  1. 2009.03.13 EJ2E Item 20. 태그가 있는 클래스 대신 클래스 계층구조를 선호하라.
  2. 2009.01.13 EJ2E Item 17. 상속에 대한 설계와 문서화를 제대로 하지 않을 거면 아예 상속을 허용하지 말라.
  3. 2008.12.28 EJ2E Item 16. 상속보다 컴포지션을 선호하라
  4. 2008.12.23 EJ2E Item 15. 변경을 최소화하라 (2)
  5. 2008.12.21 EJ2E 14. public 클래스에서는 접근 메소드를 사용하지 public 필드를 사용하지 마라.
  6. 2008.11.07 EJ2E Item 13. 클래스와 멤버 접근성을 최소화 하라
  7. 2008.11.04 EJ2E Item 12. Comparable 구현을 고려하라
  8. 2008.10.31 EJ2E Item 11. 적절하게 clone을 재정의하라
  9. 2008.10.31 EJ2E Item 10. toString은 항상 재정의하라.
  10. 2008.10.29 EJ2E Item 9. equals를 재정의할 땐 hashCode도 재정의하라.
  11. 2008.10.29 EJ2E Item 8. equals를 재정의 할 떄는 일반적인 계약을 따르라. (2)
  12. 2008.10.29 EJ2E Item 7. finalizer 사용 자제하기
  13. 2008.10.28 EJ2E Item 6. 사용하지 않는 객체 레퍼런스를 제거하라
  14. 2008.10.28 EJ2E Item 5. 불필요한 객체 생성 방지하기
  15. 2008.10.28 EJ2E Item 4. private 생성자로 객체생성 방지하기
  16. 2008.10.28 EJ2E Item 3. 싱글톤 속성은 private 성성자 또는 enum 타입으로 (2)
  17. 2008.10.27 EJ2E Item 2. 생성자에 매개변수가 너무 많을 때는 빌더를 고려하자.
  18. 2008.10.27 EJ2E Item 1. 생성자 대신 static 팩토리 메소드 사용을 고려하라.
  19. 2008.10.26 Effective Java Reloaded

EJ2E Item 20. 태그가 있는 클래스 대신 클래스 계층구조를 선호하라.

Java : 2009.03.13 20:09


참조: Effective Java 2nd Edition. Prefer class hierarchies to tageed classes

사용자 삽입 이미지

위와 같은 클래스의 단점:
- enum, switch 문, 태그 필드로 인해 지져분하다.
- 여러 구현체를 하나의 클래스로 합쳐놓았기 때문에 가독성이 떨어진다.
- 불필요한 필드까지 가지고 인스턴스를 만들어야 하기 떄문에 메모리 풋프린트가 증가한다.
- 생성자에서 불필요한 필드까지 초기화하지 않는 이상 필드를 final로 선언할 수 없다.
- 생성자에서 초기화를 잘못했을 때 컴파일 시점에 이것을 알 수 없다.
- 새로운 종류를 추가했을 때 switch문에 case를 추가해야 한다는 것을 기억해야 한다. 안그러면 런타임 에러가 발생한다.
- 인스턴스 데이터 타입이 실제 타입을 알려주지 못한다.
=> 즉 장황하고, 에러가 발생할 여지가 많고, 비효율적이다.

먼저 추상 클래스를 만들어서 일반적인 것들을 이 클래스로 이동시키고,
구체적인 하위 클래스를 정의한다.
해당 하위 클래스에서 추상 매서드를 구현한다.

사용자 삽입 이미지

이렇게 구성하면
- 깨끗하고 간단하다.
- 특정 타입에 관련된 속성은 해다 클래스가 가지게 된다. 따라서 불필요한 필드가 없다.
- 모든 필드를 final로 선언할 수 있다.
- 컴파일 시점에 생성자에서 데이터 필드를 초기화 하는지, 추상 매서드를 구현했는지 확인할 수 있다.

top

Java : 2009.03.13 20:09 Trackback. : Comment.

EJ2E Item 17. 상속에 대한 설계와 문서화를 제대로 하지 않을 거면 아예 상속을 허용하지 말라.

Java : 2009.01.13 22:38


참조: Effective Java 2nd Edition Item 17: Design and document for inheritance or else prohibit it.

오버라이딩이 가능한 메소드에는 반드시 문서화를 해야 한다.
 - "This implementation ~~" 즉 "이 구현체에서는.." 이라는 식으로 다음 배포 때는 다르게 구현할 수도 있다는 의미를 내포하는 문서화와 그 내용을 자세히 알려줘야 한다.

상속을 고려한 클래스를 테스트하는 유일한 방법은 하위클래스를 작성하는 것이다.
- 배포하기 전에 반드시 하위클래스를 작성해서 테스트 해봐야 한다.

생성자에서 오버라이딩이 가능한 메소드를 직접적으로든 간접적으로든 호출하면 안 된다.
- 상위 클래스 생성자에서 하위 클래스가 오버라이딩한 메소드를 호출하다가 아직 필드 초기화도 안 된 하위 클래스의 필드를 사용해서 원치 않던 결과가 발생할 수 있다.

Cloneable과 Serializable 인터페이스를 구현한 클래스가 상위클래스가 될 여지가 있다면
- 생성자와 마찬가지로 clone과 readObject에서 오버라이딩이 가능한 메소드를 호출하지 않도록 한다.

안전하게 상속하도록 문서화 하거나 설계하지 않은 클래스를 상속할 수 없도록 하는 것이 최선책이다.
- 두 가지 방법 중에서 쉬운 방법은 클래스를 final로 만드는 것이다.
- 두 번째 방법은 모든 생성자를 private 또는 package-private으로 만들고 public static 팩터리를 추가한다. 이 방법은 내부적으로 상속이 가능해서 약간 더 유연하다. Item 15에서 다뤘다.




top

Java : 2009.01.13 22:38 Trackback. : Comment.

EJ2E Item 16. 상속보다 컴포지션을 선호하라

Java : 2008.12.28 00:25


참조: Effective Java 2nd Edition Item 16: Favor composition over inheritence

상속
- 상속은 코드 재사용을 하는 강력한 방법이지만 항상 최선의 방법은 아니다. 무분별하게 사용했다가는 연약한 소프트웨어가 된다.

- 상속은 동일한 패키지 내에서 하위 클래스와 상위 클래스 구현을 같은 프로그래머가 관리할 때 안전하다. 또한 상속은 상속하려는 클래스가 확장을 고려해서 설계되었고 문서화 되어 있을 때 안전하다.

- 이 책에서 말하는 상속은 구현체 간의 상속이지 인터페이스 상속을 말하는 건 아니다.

- 메소드 호출과 달리 상속은 캡슐활르 위반한다. 즉 하위 클래스가 상위 클래스의 구현에 의존한다.  상위 클래스의 구현 내용이 배포를 거듭하면서 바뀔 수 있는데.. 이로 인해 하위 클래스 코드는 건드리지도 않았는데 깨질 수가 있다.

- 오직 "is-a" 관계가 성립할 때만 적당하다. 자바 플랫폼에서 이를 위반 한 사례: Stack이 Vector를 상속받았다. Properties가 Hashtable을 상속 받았다.

컴포지션
- 컴포지션을 사용하면 앞서 말한 문제(깨지기 쉬운 코드, 상위 클래스에 새로운 메소드 추가 필요)를 해결할 수 있다.

- 포워딩(흔히 역할 위임이라고 일컫는 작업을 나타내는 듯)을 한다.

- 구현의 구체적인 내용과는 의존성이 없다.

- 래퍼(wrapper) 클래스와 데코레이터 패턴

- 래퍼 클래스 단점: 래퍼 클래스는 콜백 프레임워크에서 사용하기 적당하지 않다. 래퍼 클래스로 감싼 객체는 자기가 누구한테 감싸였는지 모르기 때문에 itself(this) 형태로 호출해도 래퍼는 무시된다. (만약 상속이었다면? 다형성 때문에 결국 상위 클래스를 상속받은 하위 클래스의 객체가 넘어갔겠지만..) => SELF problem





top

Java : 2008.12.28 00:25 Trackback. : Comment.

EJ2E Item 15. 변경을 최소화하라

Java : 2008.12.23 21:52


참조: Effective Java 2nd Editio Item 15: Minimize mutability

자바에는 다양한 Immutable 클래스들이 있는데, 그 이유는.. 불변 클래스가 변하는(mutable) 클래스보다 설계하고 구현하고 사용하기 쉽기 때문이다.

불변 클래스를 만드는 다섯 가지 규칙
1. 객체의 상태를 바꾸는 메소드를 제공하지 말라.
2. 클래스를 확장할 수 없도록 해라.
3. 모든 필드를 final로 하라.
4. 모든 필드를 private으로 하라.
5. 변경 가능한 요소에는 상호 배타적으로 접근하도록 하라.(불변 클래스가 변하는 필드를 가지고 있을 때, 클라이언트가 해당 객체에 대한 레퍼런스를 가지지 못하게 하라. defensive copies를 생성자나 aceessor에서 만들고 readObject 메소드를 만들어라. @_@)

별로 안 간단해 보이는데;;; 간단하데요

불변 객체는 당연히 쓰레드-세이프 하다. 따라서 동기화 필요 없다. 자유롭게 공유해서 쓰면 된다.

단점은 매번 별도의 객체를 필요로 한다는 것이다. 비싼 객체는 좀.. 글켔군요.

상속을 막는 방법이 클래스를 final로 만드는 거 말고 좀 더 유연한 방법이 있다. 모든 생성자를 private 또는 package-private으로 만들고 public static 팩터리를 만드는 거다. 같은 패키지에서는 상속해서 다양한 구현체를 만들 수 있지만 해당 패키지 밖에서 참조할 땐 final 클래스와 마찬가지니깐... 오호.. 천젠데;;

어떤 클래스를 불편 불변 클래스로 만들어야 할까? 고민해본 적이 없네... 집에 가면서 생각해보자.. 굳이 바뀔 필요가 없는 클래스도 필요 없이 setter를 전부 만들진 않았을까..


top


EJ2E 14. public 클래스에서는 접근 메소드를 사용하지 public 필드를 사용하지 마라.

Java : 2008.12.21 19:57


참조: Effective Java 2nd Edition. Item 14: In public classes, use accessor methods, not public fields

그런 클래스에 있는 데이터 필드에 바로 접근하면 캡슐화(Item 13) 장점을 제공하지 못하게 된다.
1. 내부를 변경하면 API까지 바뀐다. 
2. 필드를 읽을 떄 어떤 보조 행위를 추가하지 못한다.
3. 불변성을 보장할 수 없다.

따라서 private 필드를 사용하고 public 접근 메소드(getter)와 (가변 클래스인 경우) setter를 제공하라.

패키지 밖에서 해당 클래스에 접근이 필요한 경우에 접근 메소드를 제공하라. 자바 플랫폼 라이브러리중에 Dimension과 Point 클래스(java.awt 패키지)가 이를 위반하고 있다.(흠.. 뭔가 대체할 만한 클래스가 있겠죠?)

불변값에 직접 전근하게 하는건 그리 나쁘지 않다. 1과 2는 어쩔 수 없는데 불변경은 보장할 수 있다.

예제코드 hour와 minute 이라는 int 타입 final 필드를 public으로 제공하고, 해당 클래스의 생성자에서 int 값 두개를 인자로 받아서 해당 final 필드에 세팅을 해줌.
top

Java : 2008.12.21 19:57 Trackback. : Comment.

EJ2E Item 13. 클래스와 멤버 접근성을 최소화 하라

Java : 2008.11.07 10:02


참조: Effective Java 2nd Edition. Item 13: Minimize the accessibility of classes and memebers

잘 설계된 모듈은 내부 데이터 및 구체적인 정보는 외부 모듈로부터 숨긴다. 깨끗하게 구현체와 API를 분리해둔다. 그런 다음 오직 API만을 사용해서만 의사소통 하고 내부 동작은 다른 모듈이 알 필요 없게 한다. => information hiding 또는 encapsulation

Information Hiding이 중요한 이유
- 모듈간의 의존도를 낮춘다. decoupling
- 독립적으로 개발, 테스트, 최적화, 사용, 이해, 수정 할 수 있다.
- 여러 모듈 병렬 개발이 가능해져서 개발 시간을 단축시킨다.
- 유지보수 비용을 낮춘다. 이해하기 쉽기 때문에 빠르게 디버깅 할 수 있다.
- 재사용성이 좋다.
- 규모가 큰 시스템 개발시 위험을 감소시켜준다.

자바가 제공하는 information hiding 기반 시설
- acess control. 클래스 인터페이스, 멤버의 접근성을 기술하는 매커니즘.
- 적당한 지시자를 사용하는 것이 관건

제 1 규칙: 각각의 클래스 또는 멤버의 접근성을 가능한한 최소한의 접근성만 보장하라.

최상위 클래스 또는 인터페이스라면 두 가지 접근 레벨만 사용하면 된다.
- package-private
- public

멤버(필드, 메소드, 내부 클래스, 내부 인터페이스)는 네 가지 접근 레벨이 가능하다
- private
- package-private
- protected
- public

private과 package-private
- 클래스 내부 구현에 관한 것이지 외부로 노출한 API에 영향을 주지 않는다.
protected와 public
- 멤버는 외부로 노출한 API며, 영원히 제공되어야 한다. 구체 구현체와 외부와를 연결해주는 의사소통 수단이 된다.

메소드 접근성과 관련된 제약 사항
- 상위 클래스의 메소드를 재정의 할 때는 상위 클래스의 접근성보다 더 낮은 접근성을 제공하면 안 된다.[JLS. 8.4.8.3]
- 이를 어기면 컴파일 에러
- 인터페이스에 정의하는 메소드는 암묵적으로 public이 된다. [JLS, 9.1.5]

테스트를 할 때, 클래스. 인터페이스, 멤버의 접근성을 조금 높이는 경향이 있다. 즉 private에서 package-private으로 올린다. 거기까진 괜찮다. 그 이상은 올리면 안 된다. 테스트 때문에 외부로 노출하는 API가 되어서는 안 된다.

인스턴스 필드는 절대로 public이면 안 된다.
- mutable 객체의 경우 필드에 저장한 값을 제한하지 못해. 이런 클래스는 thread-safe 하지 않다.
- immutable 객체를 public final로 한다면 필드를 새로운 객체로 변경할 수 없게 된다.
- static 필드의 경우도 마찬가지다. 단 예외가 있다면 상수는 public static final도 제공한다.
- 길이가 0이 아닌 배열은 항상 mutable하다. 따라서 public static final 배열을 가지고 있으면 안 된다.

public static final 배열에 대한 대안.

private static final Thing[] PRIVATE_VALUES = { ... };
public static final List<Thing> VALUES =
    Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));

private static final Thing[] PRIVATE_VALUES = { ... };
  public static final Thing[] values() {
  return PRIVATE_VALUES.clone();
}


top

Java : 2008.11.07 10:02 Trackback. : Comment.

EJ2E Item 12. Comparable 구현을 고려하라

Java : 2008.11.04 18:07


참조: Effecive Java 2nd Edition. Item 12: Consider implementing Comparable

compareTo는 Comparable 인터페이스가 제공한다. Object의 equals와 비슷하지만, 순서 비교가 가능하며, 동일성 여부도 알 수 있고 generic하다.(어떤 의미에서 generic 하다는 거지?) 이 인터페이스를 구현한 클래스는 정렬이 가능한 것으로 인식한다. 따라서 이 클래스 타입의 배열을 다음과 같이 정렬할 수 있다.

Arrays.sort(a);

String은 Comparable 인터페이스 구현했다. 알파벳 순으로 정렬 쉽게 할 수 있다.

public interface Comparable<T> {
  int compareTo(T t);
}

Generic 사용해서 타입 제한 할 수 있다.

제약사항
- 넘어온 객체보다 작을 떄는 음수 반환, 같으면 0 반환, 크면 양수 반환.
- 음수일 땐 -1, 같을 땐 0, 양수일 땐 1을 반환하는 함수 sgn(expression)이 있을 때
  -  sgn(x.compareTo(y)) == -sgn(y.compare-To(x))
  - (x.compareTo(y) > 0 && y.compareTo(z) > 0)이면, x.compareTo(z) > 0
  - x.compareTo(y) == 0 이때, sgn(x.compareTo(z)) == sgn(y.compareTo(z))
 - 권고 사항: equals 비교 했을 때 같은 객체끼리는 compareTo 결과가 0이어야 한다.

구현하기
- equals와 비슷하지만 타입 확인할 필요 없다.
- 캐스팅도 필요없다.
- null 일 떈, NullPointerException 던진다.
- 각각의 필드를 비교한다.

샘플

public int compareTo(PhoneNumber pn) {
  // Compare area codes
  if (areaCode < pn.areaCode)
    return -1;
  if (areaCode > pn.areaCode)
    return  1;
  ...

}


top

Java : 2008.11.04 18:07 Trackback. : Comment.

EJ2E Item 11. 적절하게 clone을 재정의하라

Java : 2008.10.31 15:29


참조: Effective Java 2nd Edition Item 11. Override clone judiciously

Cloneable 인터페이스는 mixin 인터페이스(Item 18)로 복제가 가능한 객체임을 나타낼 의도로 만들었다. 하지만, 그런 목적을 제공하는데 실패했다.

이번 항목에서는 어떻게 하면 잘 동작하는 clone 메소드를 구현할지에 대한 것이다.

메소드도 없는 Cloneable 인터페이스는 무엇을 해주는 건가? Object의 clone 구현체 행위를 결정한다. 만약 해당 클래스가 Cloneable을 구현했다면, Obejct의 clone 메소드는 객체의 모든 필드를 복사한 것을 반환한다. Cloneable 인터페이스를 구현하지 않았으면, CloneNotSupportedException을 던진다.

JavaSE6 java.lang.Object 표준에서 정의한 clone 메소드 general contract

x.clone() != x // true
x.clone().getClass() == x.getClass() // true
x.clone().equals(x) // true
생성자는 호출하지 않는다.

문제
- 생성자를 호출하지 않는다는 조항은 너무 강하다.
- x.clone().getClass() == x.getClass() 조항은 너무 약하다.

...중간 어렵다..-_-;;

음.. 결론은.. final이 아닌 클래스의 clone 메소드를 재정의할 땐, super.clone 호출해서 얻어온 객체를 반환해야 한다.

@Override public PhoneNumber clone() {
  try {
    return (PhoneNumber) super.clone();
  } catch(CloneNotSupportedException e) {
    throw new AssertionError();  // Can't happen
  }
}

Object가 아니라 PhoneNumber를 반환하고 있는데, 1.5 부터는 이런 코드도 괜찮다. 1.5에 covariant return type을 도입했기 떄문에 재정의하는 메소드의 반환 타입으로 원래 타입의 하위 타입을 반환할 수도 있다.

mutable 객체를 참조하는 필드(ex. private Object[] elements)를 가지고 있는 객체에서 위와 같은 clone 메소드를 사용하면 문제가 생길 수 있다. super.clone()으로 복사하면, 같은 객체를 참조하게 되고, 그럼 원복과 복사체가 동이한 객체에 대한 레퍼런스를 쥐고 있는거라 위험하다. 따라서 원본 객체와는 별개로 복사해줘야 한다.

@Override public Stack clone() {
  try {
    Stack result = (Stack) super.clone();
    result.elements = elements.clone();
    return result;
  } catch (CloneNotSupportedException e) {
    throw new AssertionError();
  }
}

clone을 재쉬적으로 호출하는 걸로도 안 될 때(ex. private Entry[] buckets)가 있다. 그럴 땐 deep copy.

코드 생략.

아.. 복잡해. 이런게 정말 필요한거야? 만약에 Cloneable 인터페이스를 구현한 클래스를 상속할 땐, 위에 있는걸 전부 신경써서 잘 동작하는 clone 메소드를 구현해야 한다.

그런 경우가 아니라면, 객체 복사 대안책을 사용하던가 아예 이런 기능을 제공하지 않아도 된다. immutable 클래스에서 객체 복사를 제공하는건 말이 안 된다. 복사체를 원본과 구분할 수가 없기 때문에.

첫번째 대안 copy constructor 또는 copy factory를 제공하는 방법이 있다.

복사 생성자(ex, public Yum(Yum yum);)
복사 팩터리(ex. public static Yum newInstance(Yum yum);)

이 방법이 clone보다 더 좋은 이유
- they don’t rely on a risk-prone extralinguistic object creation mechanism
- they don’t demand unenforceable adherence to thinly documented conventions
- they don’t conflict with the proper use of final fields
- they don’t throw unnecessary checked exceptions
- they don’t require casts

기선: clone 안 쓰는데 이거 왜케 어렵나요?
사부님 왈: 프로토타입 패턴에서 쓰는 거다.
top

Java : 2008.10.31 15:29 Trackback. : Comment.

EJ2E Item 10. toString은 항상 재정의하라.

Java : 2008.10.31 13:36


참조:  Effective Java 2nd Edition Item 10. Always override toString

JavaSE6 스펙에서 정의한 toString 제약 사항.
“a concise but informative representation that is easy for a person to read”
toString은 println이나 printf를 호출할 때 자동으로 호출된다.

toString을 구현할 때 결정 해야 하는 중요한 것 하나는 문서에서 반환 값 형식에 대해 서술할지 여부다. value class에서는 이렇게 형식을 기술해 주는게 좋은데, 모호하지 않으며, 가독성이 좋기 때문이다. 자바의 BigInteger, BigDecimal 같은 API가 이런 방법을 취하고 있다.

형식을 기술하던 말던, 어쨋거나 문서에 여러분의 의도를 분명하게 해야한다.

top

Java : 2008.10.31 13:36 Trackback. : Comment.

EJ2E Item 9. equals를 재정의할 땐 hashCode도 재정의하라.

Java : 2008.10.29 16:51


참조: Effective Java 2nd Edition Item 9. Always override hashCode when you override equals

equals를 재정의한 클래스는 반드시 hashCode를 재정의 해야 한다. 그렇지 않으면 Object.hashCode 일반적인 계약을 위반하게 된다. 이를 위반하면 hash 기반의 컬렉션(HashMap, HashSet, Hashtable)에서 제대로 동작하지 않을 것이다.

JavaSE6 Object 표준
  • hashCode를 여러번 호출 할 때마다 같은 integer를 반환해야 한다.
  • 두 객체가 equals(Object) 메소드로 동일할 때, hashCode도 같은 integer를 반환해야 한다.
  • 필수는 아니지만, equals(Object)로 가지 않은 두 객체는 hashCode를 호출했을 때 반드시 반드시 다른 integer를 반환해야 한다.
이 중에서 핵심은 두 번째 것.

HashMap에서 key로 사용하는 객체의 hashCode 값이 다르면 equals로 같은 객체여도 같은 key로 인식하지 않는다.

좋은 hashCode 만드는 방법
  • result 라는 int 변수에 0이 아닌 수를 넣는다.
  • 각각의 필드에 다음과 같은 계산식을 적용한다.
    • 필드가 boolean이면, (f ? 1 : 0)
    • 필드가 byte, char, short, int면, (int) f
    • 필드가 long이면, (int)(f ^ (f >>> 32))
    • 필드가 float면, Float.floatToIntBits(f)
    • double이면, Double.doubleToLongBits(f)를 한 다음에 long 타입 다루듯이 한 번 더 계산
    • 레퍼런스 타입이면 hashCode 호출하여 그 결과값 반환. null이면 0 반환
  • result = 31 * result + 위에서 계산한 값
  • result를 반환한다.
  • 작성후 equals로 같은 객체가 같은 hashCode를 반환하는지 단위 테스트로 검증.
equals에서 사용하지 않는 필드는 hashCode에서도 사용하면 안 된다.

example

@Override public int hashCode() {
  int result = 17;
  result = 31 * result + areaCode;
  result = 31 * result + prefix;
  result = 31 * result + lineNumber;
  return result;
}



top

Java : 2008.10.29 16:51 Trackback. : Comment.

EJ2E Item 8. equals를 재정의 할 떄는 일반적인 계약을 따르라.

Java : 2008.10.29 12:18


참조: Effective Java 2nd Edition Item 8. Obey the general contract when overriding equals

equals 메소드 재정의은 간단해 보이지만 잘못 될 여지가 많다.

equals 메소드 재정의가 필요 없는 경우
  • 클래스 특성상 각각의 객체가 유일할 때. ex) Thread
  • "논리적인 일치" 확인 기능을 제공하는지 관심 없을 때. ex) Random
  • 이미 상위 클래스에서 재정의한 equals를 재공하며, 그 로직이 현재 클래스서도 적당할 때. ex) AbstractSet, AbstractList, AbstractMap
  • 클래스가 private 또는 package-private인 경우 equals가 절대로 호출되지 않을거라는 확신이 있을 때.
equals 메소드 재정의가 필요한 경우
  • logical equality 개념이 있는 클래스
  • 보통 value class(예외, 싱글톤, Enum 타입 - Object의 equals가 곧 logical equality)
  • ex) Integer, Date
equals를 재정의할 때 따라야 하는 일반적인 계약(JavaSE6 Object 스펙)
  • Reflexive: null이 아닌 레퍼런스 값 x에 대해, x.equals(x)는 반드시 true를 반환해야 한다.
  • Symmetric: null이 아닌 레퍼런스 값 x와 y에 대해, y.equals(x)가 true를 반환 경우에 한 해서만 x.equals(y)도 true를 반환해야 한다.
  • Transitive: null이 아닌 레퍼런스 값 x, y, z에 대해, x.equals(y)가 true고 y.equals(z)가 true면 x.equals(z)도 반드시 true여야 한다.
  • Consistent: null이 나닌 레퍼런스 값 x와 y에 대해, x.equals(y)를 몇 번 호출하든지 계속해서 일관적으로 true를 반환하거나 false를 반환해야 한다.
  • null이 아닌 레퍼런스 값 x에 대해, x.equals(null)은 반드시 false를 반환한다
규칙을 어기면 다른 객체가 어떻게 동작할지 예측하기 힘들다.

고품질 equals 메소드 레서피
  • 같은 객체를 참조하는 레퍼런스가 아닌지 확인. == 사용.
  • 정당한 타입인지 확인할 떄는 instanceof 연산자를 사용,
  • 적당한 타입으로 캐스팅
  • 각각의 필드가 같은지 확인. primitive 타입은 == 사용, 레퍼런스 타입은 equals 사용
  • 메소드 작성을 마친 뒤에, symmetric, transitive, comsistent 한지 단위 테스트를 작성한다.
example

@Override public boolean equals(Object o) {
  if (o == this)
    return true;
  if (!(o instanceof PhoneNumber))
    return false;
  PhoneNumber pn = (PhoneNumber)o;
    return pn.lineNumber == lineNumber
      && pn.prefix  == prefix
      && pn.areaCode  == areaCode;
}

top


EJ2E Item 7. finalizer 사용 자제하기

Java : 2008.10.29 11:06


참조: Effective Java 2nd Edition Item 7. Avoid finalizers

Finalizer는 예측 불가능하고, 위험하며, 별로 필요없다. 자바에서 자원 반납은 try-finally 블럭에서 처리하는게 보통.

finalizer의 단점은 바로 실행한다는 보장이 없다는 것이다. 객체를 참조하는 모든 레퍼런스가 없어지는 시점과 실제 finalizer를 실행하는 사이의 텀이 불규칙적이다. 따라서 호출 시기를 고려한 어떤 작업도 finalizer에서 하면 안 된다. ex) 파일 닫기.

더딘 finalization으로 인해 심각한 문제가 발생할 수도 있다. 객체 자원 반환을 지연 시키는 경우도 인해 OutOfMemoryError가 발생할 수도 있다. (finalizer thread 우선 순위가 낮아서 계속 처리가 밀리는 듯...)

finalizer 실행 자체를 안하고 프로그램이 종료 될 수도 있다. 중요 영속 상태를 갱신하는 일을 절대로 finalizer에서 하지 말아라. 만약 DB에서 공유 자원에 대한 락 해제를 finalizer에서 했다간 전체 시스템이 정지하는 사태가 벌어질 수도 있다.

System.gc는 finalizer 실행할 가능성을 높여주긴 해도 보장하진 못한다. 그리고 finalier 실행을 보장하는 System.runFinalizersOnExit와 Runtime.runFinalizerOnExitr는 치명적인 결함이 있는데다가 deprecated돼다.

finalizer를 사용하면 심각한 성능 문제도 있다. 저자의 로컬 컴터에서 430배 정도의 성능 차이가 발생했다.

finalizer를 대신해서 명시적인 종료 메소드(explicit termination method)를 제공하라. ex) InputStream의 close 같은 메소드. 이 메소드 내부에서는 해당 객체를 더이상 사용할 수 없다는 flag를 설정하고, 다른 메소드들은 해당 flag를 참조하여 경우에 따라 IllegalStateException을 던진다.

보통 명시적인 종료 메소드는 try-fianlly 블럭으로 감싸서 예외가 발생해도 실행하도록 한다.

finalizer는 언제 사용하면 좋은가?

하나는 "safety net"으로 사용자가 명시적인 종료 메소드 실행을 깜빡한 경우에 대한 대비책이다. 비록 호출될지 안될지도 모르지만 그래도 아예 안하는 것 보단 낫다. 이런 경우 로그 메시지를 뿌려서 해당 자원이 정상적으로 종료되지 않았다는 걸 알려주면 버그를 수정할 여지를 주게된다. ex) FileInputStream, FileOutputStream, Timer, Connection 얘네가 safety net으로 finalizer를 사용하고 있다.

두 번째는 native peers다. native peers는 native 객체들은 일반 객체가 아니라서 GC 대상이 아니다. 만약 이런 native 객체들이 반드시 종료해야 하는 리소스를 가지고 있다면, 위에서 살펴본 명시적인 종료 메소드를 제공하는게 좋겠다.

"finalizer zhaining"은 자동으로 이뤄지지 않는다. 어떤 클래스가 finalizer를 가지고 있고 그 하위 클래스가 그걸 재정의 했다면 그 상위의 finalizer를 반드시 명시적으로 호출해야 한다.

// Manual finalizer chaining
@Override protected void finalize() throws Throwable {
  try {
    ... // Finalize subclass state
  } finally {
    super.finalize();
  }
}

이렇게 try-finally로 감싸야 현재 finalizer에서 예외가 발생해도 상위 클래스의 finalizer를 실행할 수 있다. 상위 클래스의 finalizer 호출을 깜빡할 수도 있는 것에 대비해서 finalizer guardian이라는 걸 사용할 수도 있는데;;;;

finalizer guardian 이건 생략; -_-;;
top

Java : 2008.10.29 11:06 Trackback. : Comment.

EJ2E Item 6. 사용하지 않는 객체 레퍼런스를 제거하라

Java : 2008.10.28 16:18


참조: Effective Java 2nd Edition Item 6. Eliminate obsolute object references

C나 C++처럼 메모리 관리를 직접 하다가 GC를 사용하는 자바로 넘어오면 편해진거 같지만, 조심하지 않으면 메모리 부족 현상이 나타날 수 있다.

public Object pop() {
  if (size == 0)
    throw new EmptyStackException();
  return elements[--size];
}

배열에서 필요없는 객체 레퍼런스를 가지고 있기 떄문에, 여러 객체를 담고 빼고 하다보면 계속 쌓여서 결국엔 메모리 부족 현상이 발생한다.

public Object pop() {
  if (size == 0)
    throw new EmptyStackException();
  Object result = elements[--size];
  elements[size] = null; // Eliminate obsolete reference
  return result;
}

명시적으로 null 로 설정하는 방법은 예외적인 경우에 한해서 사용하는게 좋다. 객체 레퍼런스를 제거하는 가장 좋은 방법은 scope를 이용하는 것이다. 자연스럽게 제거 되도록..

또 다른 메모리 부족 주요 원인은 캐시.

객체 레퍼런스를 캐시에 넣어두고 삭제하는 걸 깜빡하는 경우가 있다. 대안책으로 WeakHashMap을 사용하는 것이다.
Remember that WeakHashMap is useful only if the desired lifetime of cache
entries is determined by external references to the key, not the value.
흠.. Weak Reference를 사용하는 HashMap인것 같은데, 잘 몰겠다. API를 봐야겠네.

메모리 부족 세 번째 주요 원인은 리스너나 콜백.

콜백을 등록하고 나서 나중에 해지할 때 제대로 하지 않아서 문제 발생. 콜백을 GC 하는 최선의 방법은 WeakHashMap을 사용해서 콜렉션에 오직 weak reference만 담는 것이다.

메모리 부족 문제는 힙 프로파일러(heap profiler)를 사용해서 디버깅 하지 않는 이상 찾아내기 힘들다.

top

Java : 2008.10.28 16:18 Trackback. : Comment.

EJ2E Item 5. 불필요한 객체 생성 방지하기

Java : 2008.10.28 12:09


참조: Effective Java 2nd Edition Item 5. Avoid creating unnecessary objects

객체를 매번 새로 생성하는 것보다 재사용하는게 당연히 더 빠르고 적절한 방법이다.

String s = new String("stringette"); 이렇게 쓰지 말것.

위 방법은 매번 새로운 String 객체 생성한다.

String s= "stringette"; 이렇게 쓸 것.

이 방법은 매번 하나의 String 객체를 사용한다. 또한 이 경우 VM에 같은 문자열을 참조하는 코드가 있을 때 같은 객체를 재사용한다.[JLS, 3.10.5]

static factory method[item 1]를 사용해서 조정할 수 있다. 생성자를 사용하면 무조건 새로운 객체를 생성하지만, 이 녀석은 꼭 그래야 하진 않으니까.

mutable 객체 재활용하는 방법
- static initialization.

private static final Date BOOM_START;
private static final Date BOOM_END;

static {
  Calendar gmtCal =
    Calendar.getInstance(TimeZone.getTimeZone("GMT"));
  gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
  BOOM_START = gmtCal.getTime();
  gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
  BOOM_END = gmtCal.getTime();
}

1.5에서 불필요한 객체를 생성하게 된 경우.
- autoboxing/unboxing.
- primive 타입을 사용 선호하고, 의도하지 않은 autoboxing을 하지는 않는지 조심해야 한다.

// Hideously slow program! Can you spot the object creation?
public static void main(String[] args) {
  Long sum = 0L;
  for (long i = 0; i < Integer.MAX_VALUE; i++) {
    sum += i;
  }
  System.out.println(sum);
}

(캬오.. 위 코드에 그렇게 심각한 문제가 있었다니.. ㄷㄷㄷ...)

이번 Item을 읽으면서 주의할 것은 객체 생성이 굉장히 비싼 일이고 기피해야 할 것은 아니라는 것이다. 코드를 보다 분명하게 하고, 간단하게 하기 위해 객체 생성을 명시적으로 하는 것은 괜찮다.

반대로 비싼 객체를 자신만의 객체 풀(object pool)을 만들어서 관리하려는 건 비추한다. 객체 풀 관리하다가, 메모리 잡아먹고, 성능 문제가 생길 수도 있다.

top

Java : 2008.10.28 12:09 Trackback. : Comment.

EJ2E Item 4. private 생성자로 객체생성 방지하기

Java : 2008.10.28 11:00


참조: Effective Java 2nd Edition Item 4. Enforce noninstantiability with private constructor

때로는 static 메소드나 static 필드만 가지고 있는 클래스를 작성하고 싶을 떄가 있을 것이다. (ㅇㅇ. XXUtils 같은 클래스들이 그런 경우겠다.)

이때 abstract 클래스로 만드는 건 효과가 없다.
- 상속 받아서 객체 생성할 수 도 있으니깐..
- 또 abstract를 보면 왠지 상속받아서 사용하도록 만들어둔 클래스로 생각하게 한다.

public class UtilityClass {
  // 객체 생성 방지
  private UtilityClass(){
    throw new AssertionError();
  }
}

명시적으로 기본 생성자라를 private으로 설정한다.
- 생성자 안에서 던지는 AssertionError는 보험.
- 반직관적이기 때문에 생성자에는 주석을 달아주는게 좋다.
- 상속 받아도 소용없다. 명시적이든 암묵적이든 상위 클래스 생성자를 호출할테니까..

top

Java : 2008.10.28 11:00 Trackback. : Comment.

EJ2E Item 3. 싱글톤 속성은 private 성성자 또는 enum 타입으로

Java : 2008.10.28 09:41


싱글톤은 객체를 딱 한번만 생성하는 클래스.

1.5 전에 싱글톤을 구현하는 방법은 두 개였다. 둘 다 private 생성자와 public static 멤버를 노출한다.

첫 번째 방법: public static filed

public class Elvis {
  public static final Elvis INSTANCE = new Elvis();
  private Elvis(){...}
  ...
}

private 생성자는 딱 한 번 static final 필드인 Elvis.INSTANCE를 초기화할 때만 호출한다. 한 가지 주의 할 것이 있는데...
a privileged client can invoke the private constructor reflectively
(Item 53) with the aid of the AccessibleObject.setAccessible method
이 부분 잘 모르겠슴.

두 번째 방법: public static factory method

public class Elvis {
  private static final Elvis INSTANCE = new Elvis();
  private Elvis(){...}
  public static Elvis getInstance() { return INSTANCE; }
  ...
}

위에서 잘 모르는 부분의 문제가 이렇게 하면 발생하지 않아. (OSAF도 이런식으로 싱글톤을 만들었쥐.) 좀 더 유연하게 변경할 수 있다. Generic 타입을 고려할 수 있다.(Item 27) 이것도 모르겠군.

위의 두 가지 방법은 Serializable 하지 못한다. (static 필드는 직렬화 안 되니깐) 따라서 모든 객체 필드를 transient로 선언하거나 readResolve 메소드(Item 77)를 제공해야 한다.

세 번째 방법: Enum singleton

1.5 부터 사용할 수 있는 싱글톤 구현 방법. 캬.. 드디어 새로운 방법 등장이구나.

public enum Elvis {
  INSTANCE;
  ...
}

이 방법은 기능적으로는 public filed 접근 방법과 동일하지만, 간결하고, 여러 객체를 생성할 여지도 없으며, 직렬화도 제공한다는 점에서 차이가 있다.
a single-element enum type is the best way to implement a singleton
저자가 이 정도로 권장할 정도니까.. 사용해 봐야겠다.
top


EJ2E Item 2. 생성자에 매개변수가 너무 많을 때는 빌더를 고려하자.

Java : 2008.10.27 18:20


참조: Effective Java 2nd Edition. Item 2: Consider a builder when faced with many constructor parameters

생성자나 static factory method에 매개 변수가 너무 많고 다양할 때 기존에는 Telescoping constructor 패턴 또는 JavaBeans 패턴을 사용왔다.

telescoping constructor 패턴
- 생성자에서 다른 생성자 호출하면서 비는 매개변수에는 기본값 설정해주는 방식.
- 잘 동작은 하는데, 매개 변수를 많이 사용할 떄는 클라이언트 코드를 작성하기도 힘들고, 읽기도 힘들다.
- 위치가 바껴도 컴파일 에러가 안 남.
 
JavaBeans 패턴
- 기본 생성자만 사용하고, 필요한 속성은 세터 사용.
- 쉽게 객체 생성 가능. 코드 읽기 쉬움.
- 생성 과정이 여러 호출로 구성되어 있어서 불완전한 상태로 사용될 여지가 있다.
- 불변 객체로 만드는 걸 불가능하게 하고, 쓰레드 세이프티를 보장하려면 개발자가 추가로 신경을 써야 한다. -> freezing 이용할 수 있지만 이 방법은 거의 사용 안 해.

Builder 패턴
- 필요한 객체를 직접 만들지 않고, 필수 매개 변수를 생성자나 static factory 메소드에 넘겨서 객체를 만들고, 부가적인 속성들은 그 뒤에 설정한다. 그리고 최종적으로 build()를 호출해서 불변 객체를 생성한다.
- 클라이언트 코드
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).
calories(100).sodium(35).carbohydrate(27).build();
- 작성하기도 쉽고, 읽기도 쉽다. Ada와 Python의 named optional parameters를 사용했다.
- Abstract Factory 패턴을 구현할 때, 자바 1.5 이상의 버전에서 빌더를 사용하면, 예전처럼 런타임이 아닌 컴파일 타임에 에러를 발견할 수 있다.
- 단점: 빌더 객체 생성 비용. 다른 패턴에 비해서 좀 복잡하다. 따라서 매개 변수가 넷 이상일 때만 사용을 권장.
top

Java : 2008.10.27 18:20 Trackback. : Comment.

EJ2E Item 1. 생성자 대신 static 팩토리 메소드 사용을 고려하라.

Java : 2008.10.27 17:52


참조: Effective Java 2nd Edition. Item 1: Consider static factory methods instead of constructor

static factory method는 Factory Method 디자인 패턴이랑 같은 것이 아니다. 직접적인 관계는 없다.

장점
- 이름을 가질 수 있다.
- 매번 새로운 객체를 만들 필요가 없어진다.
- 하위 타입을 반환할 수 있다. -> service provider framework
- 타입 매개변수를 가진 클래스 객체를 생성할 때 간결하게 쓸 수 있다. -> type inference

Service Provider Framework
- Servide interface: 공급자가 구현할 인터페이스. ex) Connection
- Provider registration API: 시스템이 구현체를 등록할 때 사용. ex) DriverManager.registerDriver
- Service access API: 사용자가 서비스 객체를 얻어올 때 사용. == fexible static factory. ex) DriverManager.getConnection
- (option) Service provider interface: 공급자가 서비스 구현체의 객체를 생성할 때 구현한다. ex) Driver

단점
- public 또는 protected 생성자 없이 static factory method만 제공하는 클래스는 상속할 수 없다.
- 다른 static factory method랑 확연하게 구분이 안 된다. -> 이름을 잘 짓자.(valueOf, of, getInstance, newInstance, getType, newType)

top

Java : 2008.10.27 17:52 Trackback. : Comment.

Effective Java Reloaded

Java : 2008.10.26 09:49


참조: Parleys.com에서 Effective Java Reloaded

Generic

- PECS, Producer extends, Consumer super.
- 타입제한으로 인해 다형성같은 유연함이 떨어질 땐, PECS를 적용하면 유연하게 만들 수 있다.

Enum

- int 값을 얻고 싶을 때 ordinal()+1 을 반환하는건 유지보수 힘들다.(순서 바뀔 수도 있고, 중간 요소가 빠져있을 수도 있고, 같은 수를 반환할 수가 없다.)
- int 속성을 enum 생성자에 전달하고 그 값을 반환하도록 코딩한다.(오.. 멋지다.)
public enum Ensemble {
  SOLO(1), DUET(2), TRIO(3), ...
 
  private final int numberOfMusicians;

  Ensemble(int size){
    numberOfMusicians = size;
  }
 
  public int numberOfMusicians() {
    return numberOfMusicians;
  }

}

- int 상수 사용 문제. not typesafe, no namespace, brittle, 출력시 값이 명시적이지가 않아, 순회하기도 힘들어, 64비트 넘으면 ㄷㄷㄷ
- EnumSet을 enum이 가지고 있는 메소드에 넘긴다.
public void applyStyles(Set<Style> styles) {...}

text.applyStyles(EnumSEt.of(Style.BOLD, Style.ITALIC));

- ordinal()은 EnumSet, EnumMap 같은 곳에서 내부적으로 사용할 목적이니까, 프로그래밍 할 땐 쓰지 말도록.

- 싱글톤 퀴즈, enum으로 싱글톤 만들기
 Serializable Singleton

public enum Elvis {
  INSTANCE;
  ...
}

- enum 제약 사항
확장 못 한다. 메소드는 인터페이스를 사용해서 확장 가능
Emulated Extensible Enum

Lazy initialization

- 언제 사용하는가
성능, initialization circularity 해결하려고

- static 필드는 Holder Class 사용하기
private static class FiledHolder {
  static final FieldType field = computeFirldValue();
}

static FieldType getField(){
  return FieldHolder.field;
}

- 인스턴스 필드는 더블 체킹

private volatile FieldType field;

FieldType getField(){
  FieldType result = field;
  if(result == null){  // 1st check (no lock)
    synchronized(this){
      result = field;
      if(result == null)  // 2nd check (w/ lock)
        field = result = computeFieldValue();
    }
  }
  return result;
}





top

Java : 2008.10.26 09:49 Trackback. : Comment.