티스토리 뷰

이펙티브 자바 Effective Java 2/E
국내도서
저자 : 조슈아 블로크(Joshua Bloch) / 이병준역
출판 : 인사이트 2014.09.01
상세보기

재정의 가능 메서드를 내부적으로 어떻게 사용하는지(self-use) 반드시 문서에 남기라는 것이다.
 

/** * {@inheritDoc} * * <p>This implementation iterates over the collection looking for the * specified element. If it finds the element, it removes the element * from the collection using the iterator's remove method. * * <p>Note that this implementation throws an * <tt>UnsupportedOperationException</tt> if the iterator returned by this * collection's iterator method does not implement the <tt>remove</tt> * method and this collection contains the specified object. * * @throws UnsupportedOperationException {@inheritDoc} * @throws ClassCastException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */ public boolean remove(Object o) { Iterator<E> it = iterator(); if (o==null) { while (it.hasNext()) { if (it.next()==null) { it.remove(); return true; } } } else { while (it.hasNext()) { if (o.equals(it.next())) { it.remove(); return true; } } } return false; }

AbstractionCollection 클래스의 remove 메서드 명세에는 iterator 메서드를 재정의하면 remove가 영향을 받는다는 사실을 확실히 알 수 있다.
이 처럼 API 문서는 메서드가 하는 일이 무엇인지 명시해야지 그 일을 어떻게 하는지 명시해서는 안된다는 규칙을 깨는 이유는 계승이 캡슐화 원칙을 침해하기 때문이다.

계승에 적합한 설계를 하려면 위 처럼 문서화 작업과 클래스 내부 동자게 개입할 수 있는 훅(hooks)을
신중하게 고른 protected 메서드 형태로 제공해야 한다.

/** * Removes from this list all of the elements whose index is between * {@code fromIndex}, inclusive, and {@code toIndex}, exclusive. * Shifts any succeeding elements to the left (reduces their index). * This call shortens the list by {@code (toIndex - fromIndex)} elements. * (If {@code toIndex==fromIndex}, this operation has no effect.) * * <p>This method is called by the {@code clear} operation on this list * and its subLists. Overriding this method to take advantage of * the internals of the list implementation can <i>substantially</i> * improve the performance of the {@code clear} operation on this list * and its subLists. * * <p>This implementation gets a list iterator positioned before * {@code fromIndex}, and repeatedly calls {@code ListIterator.next} * followed by {@code ListIterator.remove} until the entire range has * been removed. <b>Note: if {@code ListIterator.remove} requires linear * time, this implementation requires quadratic time.</b> * * @param fromIndex index of first element to be removed * @param toIndex index after last element to be removed */ protected void removeRange(int fromIndex, int toIndex) { ListIterator<E> it = listIterator(fromIndex); for (int i=0, n=toIndex-fromIndex; i<n; i++) { it.next(); it.remove(); } }

removeRange 클래스는 하위 클래스가 부분 리스트에 대한 claer 메서드 실행 성능을 개선하기 쉽게 하려고 제공하는 메서드다. removeRange 클래스가 없다면, 하위 클래스에서는 부분 리스트에 대한 clear 메서드 성능이 리스트 길이의 제곱에 비례하여 나빠진다는 점을 감안하고 사용하거나, subList 메커니즘을 처음부터 끝까지 다시 작성해야 할 것이다.

계승을 위해 설계한 클래스를 테스트할 유일한 방법은 하위 클래스를 직접 만들어 보는 것이다.

계승을 허용하려면 반드시 따라야 하는 제약사항이 몇가지 더 있다.
1. 생성자는 직접적이건 간접적이건 재정의 가능 메서드를 호출해서는 안 된다는 것이다.
  => 상위 클래스 생성자는 하위 클래스 생성자보다 먼저 실행되므로, 하위 클래스에서 재정의한 메서드는 하위 클래스 생성자가 실행되기 전에 호출될 것이다. 재정의한 메서드가 하위 클래스 생성자가 초기화한 결과에 의존할 경우, 그 메서드는 원하는 대로 실행되지 않으 것이다.

public class Super { public Super() { overrideMe(); } public void overrideMe() { } } public final class Sub extends Super { private final Date date; Sub() { date = new Date(); } //상위 클래스 생성자가 호출하게 되는 재정의 메서드 @Override public void overrideMe() { System.out.println(date); } public static void main(String[] args) { Sub sub = new Sub(); sub.overrideMe(); } }

위 프로그램은 날짜가 2번 출력될 것으로 기대되지만 실제로는 처음에 출력되는 값은 NULL 이다.
첫번째 NULL 은 Sub 생성자가 호출되면서 date 필드를 초기화 하기 전에 Super의 생성자가 overrideMe 메서드를 호출해 버리기 때문이다.

호출 순서
Super 클래스 생성자 ->  overrideMe() -> Sub 클래스 생성자
overrideMe() 메서드가 호출될때 재정의된 Sub클래스의 overrideMe()가 호출되고 Sub 클래스 생성자가 아직 호출되지 않았기 때문에 date는 NULL 이다.



공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함