본문 바로가기

STUDY/이펙티브자바

4-5) 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라

상속을 고려한 문서화

 

public abstract class AbstractCollection<E> implements Collection<E> {
    ...
    /**
     * {@inheritDoc}
     *
     * @implSpec
     * 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
     * {@code UnsupportedOperationException} if the iterator returned by this
     * collection's iterator method does not implement the {@code remove}
     * method and this collection contains the specified object.
     *
     * @throws UnsupportedOperationException {@inheritDoc}
     * @throws ClassCastException            {@inheritDoc}
     * @throws NullPointerException          {@inheritDoc}
     */
     public boolean remove(Object o) {
     ...
}

상속을 고려한 클래스를 만든다면 재정의 할 수 있는 메소드들을 내부적으로 어떻게 이용하는지 문서로 남겨야한다. 이때 주석을 @implSpec과 함께 써주면 된다. AbstractCollection 클래스를 보면 remove 메소드에 @implSpec과 함께 주석이 달려있다. remove 메소드는 iterator의 반환값의 remove를 호출한다고 명시함으로써, iterator 메소드에 영향을 받는다는 사실을 알려준다.

 

 


상속용 클래스의 생성자 주의사항

 

public class Super {
    public Super() {
        overrideMe();
    }

    public void overrideMe() {
    }
}

 

public final class Sub extends Super {
    private final Instant instant;

    Sub() {
        instant = Instant.now();
    }
    
    @Override
    public void overrideMe() {
        System.out.println(instant);
    }

    public static void main(String[] args) {
        Sub sub = new Sub();
        sub.overrideMe();
    }
}

상속을 고려한 클래스의 생성자는 재정의 가능한 메소드를 호출하면 안된다. Super 클래스를 상속한 Sub 클래스가 생성될 때, Super클래스 생성자에서 overrideMe 메소드를 호출한다. 그런데 overrideMe 메소드가 Sub 클래스의 필드를 사용하도록 재정의 되어있다. 문제는 Super클래스의 생성자가 먼저 호출되면서 overrideMe 메소드를 호출하고, overrideMe가 오버라이딩되어서 아직 초기화 되지 않은 Sub클래스의 필드를 사용한다는 것이다. 초기화되지 않은 필드를 사용하는 것은 매우 위험한 동작이다. 이처럼 상속을 고려한 클래스의 생성자를 만들때는 재정의 가능한 메소드 호출이 없도록 해야하고, 이는 생성자와 비슷한 역할을 하는 clone과 readObject도 마찬가지이다.

 

 


상속을 고려하거나 금지하거나

 

이처럼 상속을 고려한 클래스를 만들기는 매우 까다롭다. 클래스가 상속에 적합한지 알기위한 명확한 규칙이 없기 때문에, 직접 하위 클래스를 구현해보면서 파악할 수 밖에 없다. 때문에 상속을 고려하지 않는 클래스라면 처음부터 이를 막는 것이 안전하다. 상속을 막기 위해서는 클래스를 final로 선언하거나 모든 생성자를 private로 선언하면 된다.

 

 

728x90