2011.04.18 20:58
  항목 16에서 상속을 위한 설계와 문서화를 하지 않은 클래스로부터 상속을 받을 때의 위험성을 경고하였다. 그렇다면 상속을 위한 설계와 문서화가 된 클래스란 무슨 의미일까?

 우선, 그런 클래스에서는 메소드 오버라이딩으로 인한 파급 효과를 분명하게 문서화해야 한다. 달리 말해 오버라이드 가능한 메소드들의 자체사용(self-use) 즉, 그 메소드들이 같은 클래스의 다른 메소드를 호출 하는지에 대해 반드시 문서화해야 한다.

 이와는 반대로, 각각의 public이나 protected 메소드 및 생성자가 어떤 오버라이드 가능한 메소드를 호출하는지, 어떤 순서로 하는지, 호출한 결과가 다음 처리에 어떤 영향을 주는지에 대해서도 반드시 문서화해야한다. 더 일반적으로 말하자면, 오버라이드 가능한 메소드를 호출 할 수 잇는 어떤 상황에 대해서도 문서화해야 한다는 것이다. 
  
 관례적으로 오버라이드 가능한 메소드를 호출하는 메소드에는 문서화 주석의 제일 끝에 그런 호출에 대한 설명을 추가한다. 그리고 설명의 시작은 "이번 구현 버전(This implementation)"이라는 구문으로 한다. 배포판이 달라지면서 메소드의 동작이 변경될 수 있다는 것을 나타내기 위해서만 그런 구문을 넣어서는 안된다. 그 구문에서는 메소드 내부에서 처리하는 작업에 관련되는 설명을 담고 있어야 한다. 

 책에서는 java.util.AbstractCollection의 명세를 예로 들고 있다. 참고하기를 바란다.

 그러나 잘된 API 문서에는 메소드가 무슨(what) 일을 하는지 기술해야 하고,   어떻게(how) 하는지를 설명하면 안된다는 통념을 어기는 것이다.
  이것은 결국 상속이 캡슐화를 위반함으로써  초래된 불행인 것이다. 서브 클래스가 안전할 수 있게끔 클래스를 문서화하려면, 클래스의 상세 구현 내역을 기술해야 한다(보통때는 하지 않지만).

 오버라이드 가능한 메소드들이 같은 클래스의 다른 메소드를 호출하는 자체 사용을 문서화 하는것 이외에도 상속을 위한 클래스 설계에는 고려할 점이 많다. 그 중 하나로써 잘 선정된 protected 메소드(또는 드물지만 protected 필드)를 제공하여 클래스 내부의 다른 메소드와 연결되도록 해야 한다.
 또한 상속 관련해서 특별히 추가되는 내용과 일반적인 내용(평범하게 클래스 인스턴스를 생성하고 메소드를 호출하는 프로그래머를 위한)이 섞임으로 API 문서를 보기가 혼란스러워 질 수 있다는것에 유의하자.

 상속을 허용하기 위해 클래스가 준수해야 하는 제약이 몇가지 더 있다. 직접 또는 간접의 어떤 형태로든 생성자에서는 오버라이드 가능한 메소드를 호출하면 안된다.

 상속을 위한 클래스를 테스트하는 유일한 방법은 서브 클래스를 만들어 보는것이다.
 그리고 그 서브클래스는 수퍼클래스 작성자가 아닌 사람이 작성해야 한다.



 상속을 위한 클래스 설계에서 Cloneable과 Serializable 인터페이스 중 어느 하나를 구현하는 상속을 위한 클래스를 설계하는 것은좋은 생각이 아니다.(textbook 항목 11과 74 참조)
 꼭 구현해야겠다면 clone과 readObject 메소드가 생성자와 흡사하게 동작하므로 생성자와 유사한 규칙이 적용된다는 것에 유의해야 한다. 즉, 직접 또는 간접의 어떤 형태로든 clone이나 readObject 모두 오버라이딩 가능한 메소드를 호출하면 안된다.
 마지막으로, 상속을 위해 설계된 클래스가 Serializable 인터페이스를 구현하기로 했는데, 그 클래스가 readResolve나 writeReplace 메소드를 가지고 있다면, 그 메소드들을 private가 아닌 protected로 해야한다. 만일 그 메소드들이 private라면 서브 클래스에서 사용할 수 없기 때문이다. 상속을 허용하기 위해 클래스 내부의 구현 메소드가 그 클래스의 API가 되어야 하는 것이다.



 많은 논점이 있었지만 중요한 것은 상속을 위해 클래스를 설게하다 보면 그 클래스에 많은 제약이 생긴다. 라는것이다. 서브 클래스를 안전하게 만들수 있도록 설계나 문서화를 제공하지 않는 클래스의 상속을 금지하는 것이 좋은 방법이라고 할 수 있겠다.
 금지하는 방법은 클래스를 final로 선언하거나, 생성자를 private 혹은 default로 두고 public static 팩토리 메소드를 추가하는것이다.(항목15)

 하지만 실제 프로그래밍에서는 실체 클래스에서 상속을 사용하는 일이 많을 것이므로 이럴 경우에는 항목 16에서 설명한 래퍼 클래스를 만들거나 어떤 오버라이드 가능한 메소드의 자체 사용(self-use)를 완전히 없애는 방법도 있다. 또는 오버라이드 가능한 메소드의 몸체코드를 private "지원(helper)메소드"로 옮기고, 오버라이드 가능한 메소드에서는 그 지원 메소드를 호출하도록 하는 방법도 있다.




 
신고
Posted by JAVA_HOME

댓글을 달아 주세요

2011.04.18 20:20
 상속은 캡슐화를 위배하기 떄문에 부실한 소프트웨어를 초래한다.(textbook에서 다루는 상속의 문제점은 구현 상속(implementation inheritance, 클래스간의 상속)에서의 문제점을 의미한다. 인터페이스 상속(interface inheritance, 클래스에서 인터페이스를 구현하거나, 인터페이스가 다른 인터페이스로 확장되는 것)에는 적용되지 않는다.)

 textbook에서 거론한 잘못된 상속 사용법은 우리가 쉽게 저지를 수 있는, 그리고 이미 저지르고 있는 방법이므로 꼭 읽어보기를 바란다.


 그렇다면 대안은 무엇이 있을까?

 상속을 하는 대신 기존 클래스(상속에서 수퍼 클래스에 해당되는)의 인스턴스를 참조하는 private 필드를 새로운 클래스(상속에서 서브 클래스에 해당되는)에 둔다. 이런 식의 설계를 컴포지션(composition)이라 하는데, 그 이유는 기존 클래스가 새 클래스의 컴포넌트로 포함되기 때문이다.

  새 클래스의 각 인스턴스 메소드에서는 포함된 기존 클래스 인스턴스의 대응되는 메소드를 호출하여 결과를 반환할 수 있다. 이것을 포워딩(forwarding)이라 하고 새 클래스의 메소드를 포워딩 메소드라 한다. 이렇게 하면, 새 클래스는 기존 클래스의 내부 구현에 종속되지 않고 강건하게 될 것이다. 기존 클래스에 새로운 매소드를 추가하더라도 새 클래스에는 영향을 주지 않는다.

 다음의 예시 코드를 보면 이해하기 쉬울 것이다. 다음 예시 코드는 두부분으로 나누어져있다. InstrumentedHashSet 클래스 코드 및 재사용 가능한 포워딩 클래스(포워딩 메소드만 갖고 있는) 코드이다.

 이러한 InstrumentedSet 클래스를 래퍼(Wrapper) 클래스라고 하는데, 그 이유는 각 InstrumentedSet 인스턴스가 다른 Set 인스턴스를 포장하듯 포함하기 때문이다. 
 이것을 데코레이터 패턴(Decorator Pattern)이라고도 하는데, InstrumentedSet 클래스가 자신의 기능을 다른 Set에 덧붙여 치장("decorates")하기 때문이다. 컴포지션과 포워딩을 결합한 것을 막연하게 위임(delegation)이라고 하는 경우도 있지만, 래퍼 객체 참조가 자신이 포함된 객체에 전달되지 않는다면 기술적으로 위임이라고 할 수 없다.
 
 래퍼 클래스의 단점은 거의 없지만, 차후에 호출("콜백")이 될 수 있도록 객체 자신의 참조를 다른 객체에게 전달하는 콜백 프레임워크(Callback framework)에서의 사용은 부적합하다. 래퍼 객체에 포함된 객체는 자신의 래퍼 객체를 알지 못하므로, 콜백을 할 경우 자신의 참조(this)만을 다른 객체에게 전달하기 때문이다. 이것은 이미 SELF 문제라고 알려져있다.

 포워딩 메소드는 작성하기 번거롭지만, 각 인터페이스에 대해 포워딩 클래스는 한번만 작성하면 되며, 관련 인터페이스가 포함된 곳과 같은 패키지에 둘 수도 있을 것이다. 

 
 컴포지션 대신 상속을 사용하기로 결정할 때는 API의 결함이 없는 super 클래스와 진정한 서브 타입 관계 즉 "is-a"관계일 때만 사용하여야 하며, 그렇지 않다면 컴포지션을 사용해야 한다.
신고
Posted by JAVA_HOME

댓글을 달아 주세요

2011.04.18 16:30
 인스턴스가 가변적이어야 할 타당한 이유가 없다면, 그 클래스는 불변 클래스가 되어야 한다.

 불변 클래스는 가변 클래스에 비해 설계와 구현 및 사용이 더 쉽고, 에러 발생이 적으며 보안이나 사용 측면에서 더 안전한 장점이 있다. 


 불변 클래스를 만들 때는 다음 다섯가지 규칙을 따르자.

1. 객체의 상태를 변경하는 그 어떤 메소드(변경자라고 하는)도 제공하지 않는다.
2. 상속(inheritance)을 할 수 없도록 하자.

더보기

3. 모든 필드를 final로 지정한다. 
4. 모든 필드를 private로 지정한다.
5. 가변 컴포넌트의 직접적인 외부 접근을 막자. 

더보기

 불변 클래스의 유일한 단점은 객체가 가질 수 있는 각 값마다 별개의 객체가 필요하다는 것이다. 

 현실적으로 불변 클래스가 되기 어려운 클래스가 있는데 이런 경우에는 가능한 가변성을 제한하자. 그리고 필드를 final로 하지 않아야 될 이유가 없는 한 모든 필드를 final로 만든다.

 책에서는 불변 클래스의 성능상 단점 및 극복방안에 대한 내용이 있는데 한번 읽어보면 도움이 될 것이다.
  
신고
Posted by JAVA_HOME

댓글을 달아 주세요

2011.04.18 16:01
 흔히 게터(getter)와 세터(setter)로 알고 있는 접근자 메소드(accessor) 메소드와 변경자(mutator)메소드를 사용한 캡슐화(encapsulation)는 모든 자바 개발자라면 하고 있을 것이다. 

 public 클래스와 같은 경우 위와 같은 방법으로 하는 것이 옳지만, 패키지 전용 클래스거나, private 중첩 클래스라면 데이터 필드를 노출해도 별로 문제 되지 않으므로 접근자 메소드를 사용하는 것보다 코드를 알아보기 쉽게 하기 위해서 노출하는 편이 좋을때도 있다. 

 한줄로 요약하면 public 클래스는 모든 가변 필드를 절대로 외부에 노출시키지 말아야 한다. 정도가 되겠다.
신고
Posted by JAVA_HOME

댓글을 달아 주세요

2011.04.18 13:28
OOP 소프트웨어 설계 기본원리 중 하나인 정보 은닉(Information hiding) 또는 캡슐화(Encapsulation)은 매우 중요하다.
 잘 설계된 모듈과 그렇지 않은 것을 구분 짓는 가장 중요한 잣대가 되며, 시스템을 구성하는 모듈들 간의 결합도를 낮추어 모듈 별로 개발, 테스트, 최적화, 사용 및 수정이 쉬워진다.

 자바에서는 정보 은닉을 지원하는 기능이 많은데, 그 중 하나인 접근 제어(access control) 메커니즘에서는 클래스와 인터페이스 및 멤버의 접근성(accessibility)을 지정하는데 그것들이 선언된 위치에 의해 접근성이 결정되며, 필요에 따라 접근 변경자(access modifier)인 private, protected, public을 선언문에 지정한다.

 멤버(필드,메소드,중첩클래스,중첩인터페이스)의 경우 위의 4가지 접근 수준이 가능하며, 접근 수준이 낮은 수준부터 높은 순으로 나열하면 다음과 같다.

- private : 멤버가 선언된 최상위 클래스 내에서만 접근 가능하다.
- (default) : 지정하지 않으면 default로 지정되는 수준이며, 멤버가 선언된 클래가 속한 패키지의 모든 클래스에서 접근 가능하다. 
- protected : default 접근 수준을 가지며, 추가적으로 멤버가 선언된 클래스의 서브 클래스로 부터 접근 가능하다.
- public : 어디에서든 접근 가능하다. 


 한가지만 기억하면된다. 각 클래스나 멤버의 접근 허용을 가능한 최소화 하는것이다. 
 
 클래스를 설계할 때는 우선적으로 public API를 신중하게 결정한 다음 그 외의 다른 멤버들은 private으로 해야 한다. 그리고 같은 패키지의 다른 클래스가 특정 멤버를 꼭 접근할 필요가 있을 떄만 그 멤버의 private 접근 지시자를 제거하여 패키지 전용이 되도록 한다. 
 접근 범위가 커질수록 지속적인 유지 보수가 필요하다는 것을 명심하자.

 메소드의 접근 수준을 줄일 수 없도록 제한 하는  규칙이 하나 있다. 서브 클래스의 메소드가 수퍼 클래스의 것을 오버라이드 하는 경우 수퍼 클래스에서 지정한 접근 수준보다 더 낮은 것을 지정할 수 없다. 그리고 이 규칙이 특별하게 적용되는 경우가 있는데, 인터페이스를 구현할 경우 반드시 public으로 선언되어야한다.


 인스턴스 필드는 절대로 public으로 하지말아야한다.
 또한 public 가변 필드를 갖는 클래스는 스레드에서 안정성을 보장할 수 없다.
 요소가 하나라도 있는 배열은 항상 가변적이라는 것에 주의하자. 따라서 클래스에서 public static final 배열 필드를 갖거나, 또는 그런 필드를 반환하는 접근자(accessor, 통칭 getter) 메소드를 갖는 것은 잘못된 것이다.

 그런 문제점을 해결하는 두 가지 방법이 있다. 하나는 public 배열을 private으로 하고, public의 불변 list를 추가하는 것이다.
 또 다른 방법으로는 배열을 private로 하고, 그 배열의 복사본을 반환하는 public 메소드를 추가하는 것이다.

신고
Posted by JAVA_HOME

댓글을 달아 주세요

2011.04.17 21:01
 클래스와 인터페이스는 자바 프로그래밍 언어의 핵심이며 추상화의 기본 단위이다. 자바에서는 클래스와 인터페이스를 설계하는데 사용할 수 있는 강력한 요소들을 제공한다. 우리가 만드는 클래스와 인터페이스가 쓸모 있고 강력하며 유연성이 있도록 하기 위해 이장에서는 그런 요소들을 최상으로 사용하는데 도움을 주는 지침을 설명한다. (textbook 인용)

  앞의 두 챕터에서는 성능이나 어떠한 메소드의 동작과 같은 국지적인 부분에 포커스를 맞추었지만(앞의 내용들이 중요하지 않다는 것은 아니다!), 이번 Chapter에서는 OOP 프로그래밍 설계를 JAVA 언어가 가진 특성을 활용하여 극대화 하는 방법 및 잘못되거나 비효율적인 방법으로 한 설계를 바로잡는 것을 다루는 Chapter 이므로 중요도가 매우 큰 Chapter라고 할 수 있겠다.
신고
Posted by JAVA_HOME

댓글을 달아 주세요

2011.04.17 20:45
  compareTo메소드는 Comparable 인터페이스에서 유일하게 존재하는 메소드이다. 비록 Object의 메소드는 아니지만, equals와 유사한 특성을 갖는다. 차이점이라면 두 객체가 동일한지를 비교하는 것과 더불어 순서까지 비교할 수 있으며, 제네릭 타입을 지원한다. 특정 클래스에서 Comparable 인터페이스를 구현한다는 것은 자신의 인스턴스들이 자연율(律)(Natural Order)을 따른 다는 것을 나타낸다. 

 Comparable 인터페이스를 구현하는 객체들이 저장된 배열의 정렬은 다음과 같이 쉽게 할 수 있다.

 Arrays.sort(a); 

  Comparable 인터페이스를 구현하는 객체들이 저장된 컬렉션을 검색하고, 최대 값을 찾고, 자동으로 정렬된 상태로 유지하는 것도 이와 유사하게 쉽다. 예를 들어, String 클래스에서 구현하는 Comparable에 의존하는 다음 프로그램에서는 명령행에서 준 인자들을 알파벳 순으로 출력하되 같은 값은 제외한다.

 
 우리 클래스에서 Comparable 인터페이스를 구현하면, 이  인터페이스에 의존하는 자바의 수많은 알고리즘 및 컬렉션 클래스들과 상호 연동이 가능하다. 실제로 자바 라이브러리의 모든 값 클래스들은 Comparable 인터페이스를 구현한다. 

 순서 판단을 위해 현재 객체(compareTo 메소드가 호출된)와 지정 객체(compareTo 메소드의 인자로 전달된)를 비교한다. 현재 객체의 값이 지정 객체보다 작으면 음수 정수 값을, 같으면 0을, 크면 양의 정수 값을 반환한다.  this(자신) - argument(비교대상)의 값을 반환한다고 생각하면 쉬울것이다. 만약 비교 할 수 없는 타입이라면 ClassCastException 예외가 발생된다.



 compareTo 메소드도 보편적 계약을 갖고 있는데 equals 메소드와 유사한 모습을 보여준다. 
 다음 설명에서 sgn(표현식)은 signum 수학함수를 나타내며, 표현식의 값이 음수면 -1을, 0이면 0, 양수면 1을 반환한다.

- 모든 x와 y에 대하여, sgn(x, compareTo(y)) == -sgn(y.compareTo(x))가 되도록 해야한다.(이것은 y.compareTo(x)가 예외를 발생시킬 때만 x.compareTo(y)가 예외를 발생시켜야 한다는 의미를 내포한다.)

- 이행적인 관계가 성립되어야 한다. 즉, (x.compareTo(y) > 0 && y.compareTo(z) > 0) 이면 x.compareTo(z) > 0 이어야 한다.

- 끝으로, x.compareTo(y) == 0 이라면, 모든 z에 대해 sgn(x.compareTo(z)) == sgn(y.compareTo(z))이 되어야 한다. 

- 반드시 요구되는 것은 아니지만, (x.compareTo(y) == 0) == (x.equals(y))가 되도록 하는 것이 좋다. 그리고 Comparable 인터페이스를 구현하면서 이 조항을 지키지 않는 클래스에서는 API문서에 나타나도록 그 사실을 분명하게 표시해야 할 것이다. 즉, 다음과 같이 하기를 권한다. "주의 : 이 클래스는 equals 메소드와 다르게 자연률 순서를 지원한다."




 compareTo 메소드의 작성 방법은 equals 메소드와 유사하지만 몇가지 중요한 차이점이 있다.
 compareTo 메소드가 정의되어있는 Comparable 인터페이스가 매개변수화 타입을 가지므로, compareTo 메소드의 인자로 전달되는 비교 객체의 타입은 컴파일 시점에서 미리 결정된다. 따라서 compareTo 메소드 내부에서 비교 객체의 타입을 확인하거나 타입 변환을 할 필요 없다. 만일 인자의 타입이 잘못되면 코드가 컴파일 되지 않기 때문이다. 만일 인자가 null이면 가능한 빨리 NullPointerException 예외를 발생시켜야 한다.


 Comparable 인터페이스를 구현하지 않고 있거나, 자연율 순서와 다른 순서 매김을 사용할 필요가 잇다면 Comparator 인터페이스를 대신 사용할 수 있다. 이때는 자신의 것을 작성하거나, 또는 String 클래스의 Comparator인 CASE_INSENSITIVE_ORDER.compare 메소드, Double.compare, Float.compare 와 같이 이미 만들어 진것을 사용하자.

 특히 부동 소수점 값을 비교할 때 관계 연산자를 사용하면 compareTo 메소드의 보편적 계약을 따르지 않기 때문에 위의 메소드를 사용하는 것이 필요하다.

 비교할 필드가 많을 경우 우선시 되는 필드의 순서대로 비교하여 동일할경우 다음 중요한 필드의 값을 비교하는 식의 방법을 쓴다. 

 비교할 때 조건문을 쓰고 임의의 정수를 return 하는 방법을 사용할 수 도 있지만, compareTo는 부호를 중요시하므로, 위에서 잠깐 언급한것 처럼 두 필드의 차를 return하는 방법을 쓴다면 더 성능이 좋은 메소드를 구현할 수 있다. 
 이 방법은 빠르지만, 두 수의 차가 Integer.MAX_VALUE(2^31 - 1) 보다 작거나 같은 범위인지를 고려해야 된다는 점을 항상 염두에 두자. 이런 실수는 대다수의 정수값에 대해서 잘 동작하기 때문에 찾기 힘들기 때문에 결함을 찾기가 쉽지 않다.

신고
Posted by JAVA_HOME

댓글을 달아 주세요

2011.04.17 16:39
 Cloneable 인터페이스는 복제를 허용하는 객체라는 것을 알리는 목적으로 사용하는 믹스인 인터페이스(mixin interface)이다.

 하지만 그런 목적에 부합하지 못하고 있다. 자신이 clone 메소드를 갖고 있지 않으며, Object 클래스의 protected인 clone 
메소드를 사용할지 여부를 결정한다.

 그리고 Cloneable 인터페이스를 구현한 효과를 보려면, 그 클래스와 그 클래스의 모든 수퍼 클래스들은 문서화된 규약을 준수해야 하는데, 그 규약이란 것이 꽤 복잡하고 강제성을 띄지 않으면서 분량도 많지 않다. 그리고 그 규약에 기인하여 만들어진 메커니즘은 자바 언어 영역을 벗어난다. 생성자를 호출하지 않고 객체가 생성되어 복제되기 때문이다.



 앞에서 살펴본 메소드들과 같이 clone에도 보편적 계약이 있는데 그 내용이 매우 빈약하다. 

- x.clone () != x 는 true를 반환한다.
- x.clone().getClass() == x.getClass() 는 true를 반환한다.
- x.clone().equals(x) 는 일반적으로 true가 될 것이지만 필수요건은 아니다.


 일반적으로 객체를 복제할 때는 그 객체의 클래스로부터 새로운 인스턴스를 생성하게 되지만, 그 때 내부 데이터 구조의 복사도 필요할 수 있다. 복제를 할 떄 어떤 생성자도 호출되지 않는다. 

 위의 계약은 많은 문제점을 내포하고 있는데 자세한 내용은 책을 참고하기 바란다.


 눈여겨 볼 점 몇가지를 소개하자면,
 1. 2번째 계약을 지키기 위해 final이 아닌 수퍼 클래스의 clone 메소드를 오버라이드 할 경우, 서브 클래스의 clone에서는 반드시 super.clone을 호출하여 얻은 객체를 반환해야 한다.

더보기


 2. Array와 같이 가변 객체를 참조하는 필드를 가지고 있을 때는 참조값만 복사하는 Shallow copy가 아니라 인스턴스 자체를 복제하는 Deep Copy를 고려하자.

더보기


 3. 복잡한 객체를 복제하는 마지막 방법은, super.clone을 호출하여 반환된 복제 객체의 모든 필드를 초기화 한 후 고주순의 메소드를 호출하여 원본 객체와 같은 상태로 재생하는 것이다.

더보기


4. 생성자 처럼, clone 메소드에서는 복제 중인 객체의 final이 아닌(달리 말해, 오버라이드 가능한) 어떤 메소드도 호출 하면 안된다.

더보기


5. Object의 서브클래스에서 clone 메소드를 오버라이딩 할 때는(특히 public으로) CloneNotSupportedException과 같은 checked외를 발생시키지 않는게 메소드를 사용하기 쉽다. 하지만 상속을 목적으로 설계된 클래스가 clone을 오버라이드 하고 있다면 clone을 protected로 지정하고 CloneNotSupportedException 예외를 발생시킨다고 메소드를 선언해야 하며, 그 클래스에서는 implements Cloneable을 선언하지 말아야한다. 그렇게 함으로써 그 클래스의 서브 클래스에서는 마치 Object로부터 직접 상속 받는것처럼 Cloneable 인터페이스의 implements 여부를 선택할 수 잇기 때문이다.

6. implements Cloneable 을 선언한 클래스가 스레드에서 안전하게 사용될 수 있게 하고자 한다면, 그 클래스의 clone 메소드는 다른 메소드처럼 반드시 올바르게 동기화 되어야 한다. Object의 clone 메소드는 동기화를 지원하지 않기 때문이다.



하지만 위와 같이 복잡하게 clone 메소드를 오버라이드 해야 하는 것일까? Cloneable을 implements하는 클래스의 서브 클래스에서는 선택의 여지가 없이 잘 동작하는 clone메소드를 구현해야하지만, 그렇지 않다면 복제 생성자나 복제 팩토리 메소드를 제공한다면(자기 자신을 타입으로 하는) 자바 언어 영역을 벗어난 형태의 위험스러운 객체 생성 메커니즘에 의존하는 clone 메소드에 비해 훨씬 강력하게 구현을 할 수 있을 것이다.

 Cloneable의 단점이 많기 때문에, 배열 복제 정도로 사용한다면 모를까, 일부 숙련 프로그래머들은 clone 메소드를 전혀 오버라이드 하지 않고 호출 하지도 않는다.

신고
Posted by JAVA_HOME

댓글을 달아 주세요

2011.04.15 21:02

 자바 API문서를 보면 toString 메소드의 보편적 계약에 다음과 같이 나와있다.

 반환되는 문자열은 '간결해야 하지만 사람이 읽기 쉬운 형태의 정보 표현' 이어야 한다.
모든 서브 클래스 들은 이 메소드를 오버라이드 할 것을 권한다.


 비록 앞에서 다뤄본 equals와 hashCode 계약을 준수하는 것 만큼 중요하지는 않지만 toString 메소드를 잘 구현하면 우리 클래스를 더욱 편하게 사용할 수 있다. 
 따라서 객체가 너무 크거나, 문자열로 변환하기 어려운 상태가 아니라면 객체의 모든 중요한 정보를 반환하면 좋다. 
 static 팩토리 메소드나 생성자에서 toString에 들어갈 정보를 인자로 받아 필드를 초기화하는 것도 좋은 방법이 되겠다.

 toString 메소드를 구현할 때 고려해야될 중요한 점은 반환 값을 다른 곳에서 사용될 수 있다는 점이다.
 따라서 반환값을 형식화 시킨다면 다른 곳에서 유용하게 사용할 수 있을 것이다. 하지만 그만큼 신경을 써야되고 차기버전에서 수정이 필요하다고 생각이 되어도 유연성을 잃게 되어 함부로 고칠 수 없게 된다.

 따라서 두가지의 선택이 가능하게 된다. 반환 값의 형식을 API에 상세하게 규정할 것인가, 규정하지 않을 것인가.

 하지만 표현 형식의 규정 여부와는 무관하게 그 의도를 명쾌하게 문서화해야한다. 그리고 toString 값에 포함되는 모든정보를 프로그램적으로 접근하는 방법을 제공하자.

 이렇게 된다면 프로그래머들은 형식에 의존하는 코드나 보존 데이터를 만들지 않을 것이고, 차후에 형식이 바뀌더라도 문제가 없을것이다. 

 추가로 javadoc 주석을 사용하여서 표현 형식을 규정하는 것과 하지 않는 예를 보자.


신고
Posted by JAVA_HOME

댓글을 달아 주세요

2011.04.15 15:56
hashCode 메소드를 제대로 오버라이드 하지 않아 코드 결함이 생기는 경우가 흔하다. 
항목 8에서 언급한 바와 같이 equals 메소드를 오버라이드 할때는 항상 hashCode 메소드를 오버라이드 해야 한다.

자바 API 문서의 Object.hashCode 메소드 명세에서 가져온 계약 사항은 다음과 같다.

- 애플리케이션 실행 중에 같은 객체에 대해 한 번 이상 호출되더라도 hashCode 메소드는 같은 정수를 일관성 있게 반환해야한다.(equals 메소드에서 비교하는 객체의 값이 변경되지 않는다면)
단, 애플리케이션이 매번 다시 실행될 때마다 일관되게 같을 필요는 없다.

- equals(Object) 메소드 호출 결과 두 객체가 동일하다면, 두 객체 각각에 대해 hashCode 메소드를 호출했을 때 같은 정수 값이 나와야 한다.

-equals(Object) 메소드 호출 결과 두 객체가 다르다고 해서 두 객체 각각에 대해 hashCode 메소드를 호출했을 때 반드시 다른 정수값이 나올 필요는 없다. 그러나 같지 않은 객체들에 대해 hashCode메소드에서 서로 다른 정수값을 반환한다면, 이 메소드를 사용하는 hash 컬렉션들의 성능을 향상시킬 수 있음을 알아야된다.


 이 중 중요한 조항은 두 번째의 동일한 객체들은 같은 해시 코드 값을 가져야 한다는 것이다.

 특히 HashMap과 HashSet 및 Hashtable을 포함하는 모든 hash 기반의 컬렉션들과 우리 클래스를 같이 사용할 때 잘못된 hashCode 메소드는 우리 클래스가 올바르게 동작하지 않게 만들 것이다.


 또한 Hash algorithm을 책이나 검색을 통해 알아보는 것이 좋다. 이는 hash 컬렉션 들의 속도를 향상 시킬수 있을 것이다. 절대로 임의의 Magic Number를 사용해서는 안된다
신고
Posted by JAVA_HOME

댓글을 달아 주세요


티스토리 툴바