CodeGym /행동 /JAVA 25 SELF /인터페이스와 추상 클래스의 차이

인터페이스와 추상 클래스의 차이

JAVA 25 SELF
레벨 20 , 레슨 1
사용 가능

1. 추상 클래스: 기초 복습

비교로 넘어가기 전에, 추상 클래스가 무엇인지 간단히 상기해 봅시다.

추상 클래스는 직접 인스턴스를 만들 수 없는 클래스입니다(new Animal()처럼 쓸 수 없음). 대신 구현이 있는 일반 메서드와 구현이 없는 추상 메서드를 모두 가질 수 있습니다. 추상 클래스는 다른 클래스의 기반으로 자주 사용되며, 이들 자식 클래스는 공통 동작을 상속받거나 특정 메서드 구현을 강제받습니다.

예를 들어 애플리케이션에 여러 종류의 교통수단이 있다면, 추상 클래스 Transport를 둘 수 있습니다:

public abstract class Transport {
    private String model;

    public Transport(String model) {
        this.model = model;
    }

    public String getModel() {
        return model;
    }

    // 추상 메서드 — 구현은 없고 선언만 있음
    public abstract void move();

    // 일반 메서드 — 구현이 있음
    public void printInfo() {
        System.out.println("교통수단 모델: " + model);
    }
}

추상 클래스의 특징:

  • 필드(상태)를 가질 수 있음.
  • 구현된 메서드를 가질 수 있음.
  • 추상 메서드를 가질 수 있음(자식에서 구현해야 함).
  • 직접 인스턴스를 만들 수 없음.
  • 공통 동작과 상태를 제공하는 데 사용됨.

2. 인터페이스: 기초 복습

인터페이스는 어떤 클래스가 구현해야 하는 메서드의 집합입니다. 인터페이스는 상태를 설명하지 않으며(일반 필드 불가, 상수만 가능) Java 8 이전에는 메서드 구현을 포함하지 않습니다. 인터페이스는 순수한 계약으로, “나를 구현한다면 이런 능력을 가져야 한다”를 의미합니다.

인터페이스 예시:

public interface Movable {
    void move(int x, int y);
}

인터페이스의 특징:

  • 상태를 가지지 않음(public static final 상수만 가능).
  • Java 8 이전에는 오직 추상 메서드만 존재(Java 8부터 defaultstatic 메서드가 추가됨 — 자세한 건 뒤에서).
  • 메서드는 기본적으로 항상 public abstract.
  • 하나의 클래스가 여러 인터페이스를 구현할 수 있음.
  • 클래스가 “무엇을 할 수 있는지”를 기술하는 데 사용됨.

3. 비교 표: 추상 클래스 vs 인터페이스

이제 두 도구를 직접 비교해 봅시다! 아래 표를 참고하세요:

특징 추상 클래스 인터페이스
문법
abstract class
interface
인스턴스를 생성할 수 있나요? 아니요 아니요
구현된(일반) 메서드를 가질 수 있나요? Java 8 이전: 아니요, Java 8부터: default/static
추상 메서드를 가질 수 있나요? 예(Java 8 이전에는 모든 메서드가 추상)
필드(상태)를 가질 수 있나요? 예(어떤 필드든 가능) public static final만(상수)
생성자를 가질 수 있나요? 아니요
상속 추상/일반 클래스 하나만 상속 가능 여러 인터페이스를 구현 가능
메서드 접근 제한자 제한 없음(public, protected, private) 기본적으로 public abstract. Java 9부터 인터페이스 내부에서 사용할 private 메서드 추가 가능
상속/구현 키워드
extends
implements
주 사용 목적 공통 구현과 상태 제공 능력, 역할의 기술
JDK 예시
AbstractList, AbstractMap
Comparable, Runnable

4. 인터페이스와 추상 클래스는 언제 사용할까?

인터페이스는 다음과 같을 때 사용하세요:

  • 구현 방식에는 신경 쓰지 않고, 클래스가 “무엇을 할 수 있는지”만 기술하고 싶을 때.
  • 클래스가 서로 독립적인 여러 능력을 동시에 가져야 할 때.
  • 예: Comparable(비교 가능), Serializable(직렬화 가능), Runnable(스레드에서 실행 가능).

추상 클래스는 다음과 같을 때 사용하세요:

  • 모든 자식에게 공통으로 줄 구현과 상태를 정의하고 싶을 때.
  • 모든 자식이 특정 필드나 구현이 있는 메서드를 갖도록 강제하고 싶을 때.
  • 상속은 “하나 대 하나”로 제한될 때: 클래스는(일반이든 추상이든) 오직 한 클래스만 상속할 수 있음.

일상적인 비유

  • 인터페이스는 “운전면허”와 같습니다. 면허가 있으면 차를 운전할 수 있지만, 어떤 차를 어떻게 운전하는지는 규정하지 않습니다.
  • 추상 클래스는 “자동차의 공통 설계도”와 같습니다. 모든 자동차에는 운전대, 페달, 엔진이 있지만, 각 브랜드는 세부 구현이 다릅니다.

5. Java 표준 라이브러리의 예

인터페이스: Comparable

public interface Comparable<T> {
    int compareTo(T o);
}

이 인터페이스를 구현하는 클래스는 반드시 compareTo 메서드를 구현해야 합니다. 예를 들어 String, Integer, LocalDate 등이 있습니다.

추상 클래스: AbstractList

public abstract class AbstractList<E> implements List<E> {
    // 일부 List 메서드의 기본 구현
    // 일부 메서드는 추상으로 남겨둠
}

AbstractList는 컬렉션의 일부 동작(예: 추가/삭제 메서드)을 이미 구현해 두지만, 몇몇 메서드는 자식이 각자 방식으로 구현할 수 있도록 추상으로 남겨 둡니다.

6. 코드 예제: 실제 비교

인터페이스

인터페이스와 이를 구현하는 클래스를 만들어 봅시다.

public interface Printable {
    void print();
}

public class Document implements Printable {
    @Override
    public void print() {
        System.out.println("문서를 인쇄합니다...");
    }
}

추상 클래스

이제 추상 클래스와 그 자식을 만들어 봅시다.

public abstract class Machine {
    public void turnOn() {
        System.out.println("기계가 켜졌습니다.");
    }

    public abstract void work();
}

public class Printer extends Machine {
    @Override
    public void work() {
        System.out.println("프린터가 인쇄 중입니다...");
    }
}

클래스가 둘 다 사용: 인터페이스 구현 + 추상 클래스 상속

public class SmartPrinter extends Machine implements Printable {
    @Override
    public void work() {
        System.out.println("스마트 프린터가 작동 중입니다...");
    }

    @Override
    public void print() {
        System.out.println("스마트 프린터가 인쇄 중입니다...");
    }
}

7. 여러 인터페이스 구현: 왜 유용한가

Java에서 클래스는 (추상/일반) 클래스 한 개만 상속할 수 있지만, 인터페이스는 얼마든지 구현할 수 있습니다! 이를 통해 유연하고 확장 가능한 아키텍처를 만들 수 있습니다.

public interface Scannable {
    void scan();
}

public class MultiFunctionPrinter extends Machine implements Printable, Scannable {
    @Override
    public void work() {
        System.out.println("복합기가 작동 중입니다...");
    }

    @Override
    public void print() {
        System.out.println("복합기가 인쇄 중입니다...");
    }

    @Override
    public void scan() {
        System.out.println("복합기가 스캔 중입니다...");
    }
}

언제 무엇을 선택할까?

  • 공통 상태(예: 필드)와 기본 기능을 설계한다면 추상 클래스를 사용하세요.
  • 객체에 “능력 라벨”(예: “인쇄 가능”, “비교 가능”, “직렬화 가능”)을 부여하고 싶다면 인터페이스를 사용하세요.
  • 잘 모르겠다면 인터페이스부터 시작하는 것이 좋습니다. Java에서는 인터페이스가 더 큰 유연성과 확장성을 제공합니다.

8. 흔한 실수와 주의점

오류 1: 여러 클래스를 상속하려고 함 — Java는 허용하지 않습니다!
클래스는 오직 하나의 클래스만 상속할 수 있지만, 여러 인터페이스를 구현할 수 있습니다. 예를 들어 class A extends B, C는 오류이지만, class A extends B implements X, Y, Z는 가능합니다.

오류 2: 인터페이스의 필드와 클래스의 필드를 혼동함.
인터페이스에는 상수(public static final)만 선언할 수 있습니다. 예를 들어 private int count; 같은 일반 상태를 선언할 수 없으며, 컴파일러가 즉시 잡아냅니다.

오류 3: 인터페이스의 모든 메서드를 구현하지 않음.
클래스가 인터페이스의 메서드 중 하나라도 구현하지 않으면 그 클래스는 abstract로 선언되어야 하며, 그렇지 않으면 컴파일 오류가 발생합니다.

오류 4: 인터페이스나 추상 클래스의 인스턴스를 만들려고 함.
둘 다 “반제품”에 가깝습니다. 확장해서 써야 하며, 직접 생성할 수는 없습니다:

Printable p = new Printable(); // 오류!
Machine m = new Machine();     // 오류!

오류 5: 인터페이스에 생성자가 있을 수 있다고 생각함.
인터페이스는 객체의 상태를 정의하지 않으므로 생성자를 가질 수 없습니다. 생성자는 (일반 및 추상) 클래스에만 존재합니다.

1
과제
JAVA 25 SELF, 레벨 20, 레슨 1
잠금
운송 회사: 통일된 이동 프로토콜
운송 회사: 통일된 이동 프로토콜
1
과제
JAVA 25 SELF, 레벨 20, 레슨 1
잠금
대학 등록부: 인물과 식별자
대학 등록부: 인물과 식별자
1
과제
JAVA 25 SELF, 레벨 20, 레슨 1
잠금
사무용 다기능 장치
사무용 다기능 장치
1
과제
JAVA 25 SELF, 레벨 20, 레슨 1
잠금
스마트폰: 일반 장치에서 개인 기기로
스마트폰: 일반 장치에서 개인 기기로
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION