-
메서드 재정의시 발생하는 일을 정확히 정리
-
상속용 클래스는 재정의할 수 있는 메서드들을 내부적으로 어떻게 이용하는지 문서로 남겨야 함
-
클래스의 API로 공개된 메서드에서 클래스 자신의 또 다른 메서드를 호출할 수도 있음
- 호출되는 메서드가 재정의 가능 메서드라면, 그 사실을 호출하는 메서드의 API 설명에 적시
- 어떤 순서로 호출하는지, 각각의 호출 결과가 이어지는 처리에 어떤 영향을 주는지 정리
- ‘재정의 가능’: public, protected 메서드 중 final이 아닌 모든 메서드를 뜻함
- 재정의 가능 메서드를 호출할 수 있는 모든 상황을 문서로 남겨야 함
- ex) 백그라운드 스레드, 정적 초기화 과정에서도 호출이 일어날 수 있음
-
@implSpec 태그
- API 문서의 메서드 설명 끝에 보이는 “Implementation Requirements”로 시작하는 절
- 해당 메서드의 내부 동작 방식을 설명하는 곳임.
- 해당 태그를 붙여주면 자바독 도구가 생성해준다
-
이처럼 내부 메커니즘을 문서로 남기는 것만이 상속을 위한 설계의 전부는 아님
- 클래스의 내부 동작 과정 중간에 끼어들 수 있는 훅(hook)을 잘 선별해 protected 메서드 형태로 공개
- 어떤 메서드를 protected로 노출? → 잘 예측한 후 실제 하위 클래스를 만들어 시험해본다
- 상속용으로 설계한 클래스는 배포 전에 반드시 하위 클래스를 만들어 검증해야 함
-
상속용 클래스의 생성자는 직접적, 간접적 모두 재정의 가능 메서드를 호출하면 안된다.
- 상위 클래스의 생성자가 하위 클래스의 생성자보다 먼저 실행되므로, 하위 클래스에서 재정의한 메서드가 하위 클래스의 생성자보다 먼저 호출된다.
// 잘못된 예 - 생성자가 재정의 가능 메서드를 호출함 // 상위 클래스 코드 public class Super { public Super() { overrideMe(); } public void overrideMe() { } } // 하위 클래스 코드 public final class Sub extends Supper { 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(); } }
- 위 코드는 instant를 두 번 출력하지 않고, 첫번째에서는 null을 출력함
- 상위 클래스의 생성자는 하위 클래스의 생성자가 인스턴스 필드를 초기화하기도 전에 overrideMe를 호출하기 때문
-
상속용으로 설계하지 않는 클래스는 상속을 금지한다.
- 상속을 금지하는 방법
- 클래스를 final로 선언
- 모든 생성자를 private이나 package-private으로 선언 후 public 정적 팩터리를 만들어준다
- 상속을 금지하는 방법
-
3줄 요약
- 상속용 클래스를 설계하려면 클래스 내부에서의 자기사용 패턴을 모두 문서로 남겨야 하며, 문서화된 것은 그 클래스가 쓰이는 한 반드시 지켜져야 한다. 그렇지 않으면 하위 클래스들이 오동작한다.
- 그러니 클래스를 확장해야 할 명확한 이유가 없다면 상속을 금지하는 편이 낫다.
- 상속을 금지하려면 fina선언, 생성자 모두를 외부에서 접근할 수 없도록 만들면 된다.