공공부하자개발 · 영어 학습 노트
자바
중급객체지향과 코어 라이브러리0/9 완료
  • 01상속, 다형성, 오버라이딩
  • 02추상 클래스 vs 인터페이스
  • 03예외 처리
  • 04java.lang 심화
  • 05컬렉션 프레임워크 딥다이브
  • 06메서드 활용 패턴 (중급)
  • 07Object 메서드와 비교
  • 08java.time 실무 날짜 계산
  • 09HTTP 와 JSON 기초
사이트 소개개인정보처리방침연락처
© 2026 공부하자
홈 › 중급 › 02 / 9

추상 클래스 vs 인터페이스

섹션 7진행 0 / 9
1왜 배우는가2핵심 원리3코드 예제4응용 변형 예제5자주 하는 실수 (Tip)6연습 문제7정리‹ 이전다음 ›

5. 자주 하는 실수 (Tip)

❌ 실수 1: 구현체 타입으로 변수를 선언

java
ArrayList<String> names = new ArrayList<>();      // 구현체 타입
CardGateway gateway = new CardGateway();          // 구현체 타입

나중에 LinkedList나 BankTransferGateway로 바꾸려면 변수 타입까지 고쳐야 하고, 그 변수를 받는 메서드 시그니처도 연쇄적으로 바뀐다. 다형성의 이점을 스스로 버리는 것이다.

java
List<String> names = new ArrayList<>();           // 인터페이스 타입
PaymentGateway gateway = new CardGateway();       // 계약 타입

❌ 실수 2: 인터페이스 상수를 "설정값 공유"에 남용

java
interface AppConstants {
    String DB_URL = "jdbc:...";
    int TIMEOUT = 30;
}
class UserService implements AppConstants { /* DB_URL 쓰려고 구현 */ }

UserService는 AppConstants라는 "능력"을 갖는 게 아니다. 상수를 쓰려고 타입 계층을 오염시키는 안티패턴(상수 인터페이스 안티패턴)이다.

java
final class AppConstants {
    private AppConstants() {}
    static final String DB_URL = "jdbc:...";
    static final int TIMEOUT = 30;
}
// 사용: AppConstants.DB_URL  또는  import static

❌ 실수 3: 템플릿 메서드를 final로 안 막아서 흐름이 깨짐

java
abstract class Exporter {
    public void export() { validate(); write(); notifyDone(); }   // final 아님
}
class LazyExporter extends Exporter {
    @Override
    public void export() { write(); }   // validate 건너뜀 — 골격이 무너짐
}

템플릿 메서드의 존재 이유는 "순서를 보장"하는 것이다. 자식이 오버라이딩할 수 있으면 보장이 사라진다.

java
abstract class Exporter {
    public final void export() { validate(); write(); notifyDone(); }
    protected abstract void write();
    protected void validate() {}      // 훅으로 세부 조정만 허용
    protected void notifyDone() {}
}

❌ 실수 4: 추상 클래스에 억지로 무관한 기능을 몰아넣음

java
abstract class BaseEntity {
    Long id;
    abstract void validate();
    abstract String toCsv();
    abstract void sendNotification();   // 엔티티가 알림까지?
}

하나의 부모에 여러 책임을 넣으면 모든 자식이 필요 없는 메서드까지 구현해야 한다(빈 구현이 늘어난다). 능력은 인터페이스로 잘게 나눈다(인터페이스 분리 원칙).

java
abstract class BaseEntity { Long id; }
interface Validatable { void validate(); }
interface CsvExportable { String toCsv(); }
class Order extends BaseEntity implements Validatable, CsvExportable { /* 필요한 것만 */ }

❌ 실수 5: default 메서드에서 구현체의 상태를 가정

java
interface Cache {
    default void clearAll() {
        // 구현체에 어떤 저장소가 있는지 모르는데 "다 지운다"는 건 할 수 없음
        throw new UnsupportedOperationException();   // 이런 default는 함정
    }
}

인터페이스는 상태가 없다. default 메서드는 다른 추상 메서드를 조합해서 구현할 수 있는 것만 제공해야 한다.

java
interface Cache {
    Set<String> keys();
    void remove(String key);
    default void clearAll() {                    // 계약된 메서드만으로 구현
        for (String k : Set.copyOf(keys())) remove(k);
    }
}
자주 하는 실수 (Tip)
  • ❌ 실수 1: 구현체 타입으로 변수를 선언
  • ❌ 실수 2: 인터페이스 상수를 "설정값 공유"에 남용
  • ❌ 실수 3: 템플릿 메서드를 final로 안 막아서 흐름이 깨짐
  • ❌ 실수 4: 추상 클래스에 억지로 무관한 기능을 몰아넣음
  • ❌ 실수 5: default 메서드에서 구현체의 상태를 가정
이전 섹션4 응용 변형 예제5 / 7다음 섹션6 연습 문제