자바 개발자 면접에서 거의 빠지지 않는 질문이 있습니다. “인터페이스와 추상클래스의 차이를 설명해 주세요.” 막상 답하려 하면 머릿속에서 뒤섞이는 두 개념. 인터페이스 추상클래스 차이를 단순히 문법 차이로 외웠다면 실전 설계에서 어느 것을 골라야 할지 흔들립니다. 이 글에서는 개념의 뿌리부터 시작해 문법·사용 목적·실전 적용 기준까지, Java 코드 예시와 함께 단계별로 정리합니다. 읽고 나면 면접 답변은 물론, 실제 코드에서 어떤 상황에 무엇을 써야 하는지 확실히 판단할 수 있게 됩니다.
목차
- 추상화란 무엇인가? — 인터페이스·추상클래스를 이해하기 위한 전제
- 추상클래스(Abstract Class)란? — 미완성 설계도의 원리
- 인터페이스(Interface)란? — 행동 규약의 원리
- 인터페이스 vs 추상클래스 핵심 차이 완전 비교표
- 언제 인터페이스를, 언제 추상클래스를 써야 하는가
- 면접 답변 공식과 자주 나오는 심화 질문
1. 추상화란 무엇인가? — 인터페이스·추상클래스를 이해하기 위한 전제
추상화(Abstraction)의 개념
인터페이스와 추상클래스를 이해하려면 먼저 객체지향 프로그래밍의 핵심 원칙 중 하나인 추상화를 알아야 합니다. 추상화는 복잡한 내부 구현을 숨기고 사용자(다른 코드)에게 꼭 필요한 부분만 보여주는 것입니다.
예를 들어 자동차를 운전할 때 우리는 핸들을 돌리고 가속 페달을 밟습니다. 엔진이 어떻게 폭발하고 변속기가 어떻게 맞물리는지는 몰라도 됩니다. 이처럼 “무엇을 할 수 있는가(인터페이스)”와 “어떻게 하는가(구현)”를 분리하는 것이 추상화입니다.
Java에서 이 추상화를 구현하는 두 가지 핵심 도구가 바로 추상클래스와 인터페이스입니다.
두 개념의 공통점 — 왜 헷갈리는가
추상클래스와 인터페이스는 상속받는 클래스 혹은 구현하는 인터페이스 안에 있는 추상 메서드를 구현하도록 강제합니다. 이 공통점 때문에 처음 배울 때 두 개념이 헷갈립니다. 둘 다 직접 인스턴스를 만들 수 없고, 둘 다 하위 클래스가 반드시 특정 메서드를 구현하도록 강제합니다. Molit
그러나 존재하는 이유와 사용 목적이 다릅니다. 추상클래스는 “~이다(is-a)” 관계, 즉 상속 계층 구조에서 공통 코드를 공유하기 위해 쓰입니다. 인터페이스는 “~할 수 있다(is able to)” 관계, 즉 서로 관계 없는 클래스들 사이에 특정 동작을 보장하는 계약을 맺기 위해 쓰입니다.
2. 추상클래스(Abstract Class)란? — 미완성 설계도의 원리
추상클래스의 정의
추상 클래스는 하나 이상의 추상 메서드를 포함하고 있으며, 일반 메서드도 포함할 수 있습니다. abstract 키워드로 선언하며, 추상 메서드는 몸통(구현부) 없이 선언만 합니다. 하위 클래스가 이 메서드를 반드시 오버라이딩(Override)해서 구현해야 합니다. Daishin
java
// 추상클래스 선언
abstract class Animal {
String name; // 필드(멤버 변수) 가능
int legs;
// 일반 메서드 — 구현 있음
void eat() {
System.out.println(name + "이(가) 먹는다");
}
// 추상 메서드 — 구현 없이 선언만
abstract void sound();
}
// 하위 클래스 — 추상 메서드 반드시 구현
class Dog extends Animal {
@Override
void sound() {
System.out.println("멍멍!");
}
}
class Cat extends Animal {
@Override
void sound() {
System.out.println("야옹!");
}
}
이 예시에서 Animal은 Dog와 Cat의 공통 특성(이름, 다리 수, 먹는 행동)을 미리 구현해서 코드 중복을 없앱니다. sound()처럼 종류마다 달라지는 행동은 추상 메서드로 선언해 각 하위 클래스가 자신에 맞게 구현하도록 강제합니다.
추상클래스의 핵심 특징
추상클래스는 필드, 생성자, 추상 메서드를 가질 수 있고, 인터페이스와 다르게 필드도 가질 수 있습니다. Kareit
abstract키워드로 클래스와 추상 메서드를 선언extends키워드로 상속 → 단일 상속만 가능- 일반 메서드와 추상 메서드를 동시에 가질 수 있음
- **멤버 변수(필드)**와 생성자 보유 가능
- 접근 제어자 제한 없음 (public, protected, private, default 모두 가능)
- 직접 인스턴스 생성 불가 (
new Animal()불가)
추상클래스는 추상 메서드를 하나라도 가져야 하는 것은 아닙니다. 단, 추상 메서드를 하나라도 가지는 클래스는 반드시 추상 클래스가 되어야 합니다. Kareit
3. 인터페이스(Interface)란? — 행동 규약의 원리
인터페이스의 정의
인터페이스란 상수(static final)와 추상 메서드(abstract method)의 집합입니다. 생성자를 가질 수 없으며 따라서 객체화가 불가능합니다. Wikidocs
인터페이스는 “이 기능을 반드시 제공해야 한다”는 계약서입니다. 구현 클래스는 인터페이스에 정의된 모든 메서드를 반드시 구현해야 합니다.
java
// 인터페이스 선언
interface Flyable {
// 상수 선언 — public static final 자동 적용
int MAX_ALTITUDE = 10000;
// 추상 메서드 — public abstract 자동 적용
void fly();
void land();
// Java 8 이상: default 메서드 — 구현 가능
default void takeOff() {
System.out.println("이륙 준비 중...");
fly();
}
}
interface Swimmable {
void swim();
}
// 인터페이스 다중 구현
class Duck extends Animal implements Flyable, Swimmable {
@Override
public void sound() { System.out.println("꽥꽥!"); }
@Override
public void fly() { System.out.println("오리가 날아오른다"); }
@Override
public void land() { System.out.println("오리가 착지한다"); }
@Override
public void swim() { System.out.println("오리가 헤엄친다"); }
}
오리(Duck)는 동물이기도 하고(extends Animal), 날 수 있고(implements Flyable), 헤엄칠 수 있습니다(implements Swimmable). 추상클래스는 하나만 상속받을 수 있지만, 인터페이스는 여러 개를 동시에 구현할 수 있습니다.
인터페이스의 핵심 특징
인터페이스는 추상 메서드만을 포함하며, 일반 메서드나 변수는 가질 수 없습니다. 접근 제어자는 기본적으로 public만을 허용합니다. Daishin
interface키워드로 선언implements키워드로 구현 → 다중 구현 가능 (핵심 차이!)- 모든 메서드는 기본적으로
public abstract(생략 가능) - 모든 변수는
public static final(상수만 가능) - 생성자 없음
- Java 8 이상:
default메서드(구현 있음),static메서드 추가 가능 - Java 9 이상:
private메서드 추가 가능
인터페이스는 다중 상속을 지원하며, 구현체에 여러 개의 인터페이스를 구현할 수 있습니다. 이 다중 구현 능력이 인터페이스가 존재하는 가장 큰 이유 중 하나입니다. Wikidocs
4. 인터페이스 vs 추상클래스 핵심 차이 완전 비교표
문법·구조 차이 한눈에 비교
| 비교 항목 | 추상클래스 (Abstract Class) | 인터페이스 (Interface) |
|---|---|---|
| 선언 키워드 | abstract class | interface |
| 구현 키워드 | extends | implements |
| 다중 사용 | 단일 상속만 가능 | 다중 구현 가능 |
| 일반 메서드 | ✅ 가능 | ❌ 불가 (Java 8+ default 예외) |
| 추상 메서드 | ✅ 가능 | ✅ 가능 (기본) |
| 멤버 변수 | ✅ 가능 | ❌ 불가 (상수만 가능) |
| 생성자 | ✅ 가능 | ❌ 불가 |
| 접근 제어자 | 모두 가능 | public만 (메서드) |
| 인스턴스 생성 | ❌ 불가 | ❌ 불가 |
| 상태(State) 관리 | ✅ 가능 | ❌ 불가 |
개념·목적 차이
| 관점 | 추상클래스 | 인터페이스 |
|---|---|---|
| 관계 표현 | “~이다 (is-a)” — 상속 계층 | “~할 수 있다 (is able to)” — 기능 계약 |
| 코드 공유 | 공통 코드를 상위에서 내려줌 | 코드 공유 없음, 규약만 정의 |
| 결합도 | 상대적으로 강한 결합 | 느슨한 결합 (유연함) |
| 설계 은유 | 미완성 설계도 | 기본 설계도(밑그림) |
| 주 사용 상황 | 밀접하게 관련된 클래스 계층 | 관련 없는 클래스에 공통 기능 부여 |
추상클래스는 서로 관련된 클래스들 간의 공통된 행동을 추상화하여 공유하기 위해 사용됩니다. 인터페이스는 서로 관련이 없는 클래스들에게 특정한 동작을 강제하기 위해 사용됩니다. Daishin
5. 언제 인터페이스를, 언제 추상클래스를 써야 하는가
추상클래스를 선택해야 하는 경우
굉장히 밀접하게 관련된 클래스들끼리 코드를 공유해야 할 때, 추상클래스의 하위 구현체 클래스들이 공통된 필드나 메서드를 많이 공유하고 접근 제어자가 public이 아닌 경우, non-static 또는 non-final 필드로 객체의 상태를 바꿔야 하는 경우에 추상클래스를 사용합니다. Thepublic
쉽게 말하면 이런 경우입니다.
추상클래스 선택 기준:
- 상속받는 클래스들이 공통 필드(상태)나 코드를 많이 공유할 때
- 관련 클래스들이 명확한 계층 구조(부모-자식)를 형성할 때
- 하위 클래스들이 공통 동작의 일부만 재정의하면 될 때 (템플릿 메서드 패턴)
- 접근 제어자를 다양하게 사용해야 할 때 (protected, private 필드)
실제 예시 — 게임 캐릭터 설계:
java
abstract class GameCharacter {
// 공통 상태 — 추상클래스만 멤버 변수 가질 수 있음
protected String name;
protected int hp;
protected int mp;
// 공통 구현 — 모든 캐릭터가 동일하게 동작
void move(int x, int y) {
System.out.println(name + " 이동: " + x + ", " + y);
}
// 캐릭터마다 다른 스킬 — 하위 클래스가 구현
abstract void useSkill();
}
class Warrior extends GameCharacter {
@Override
void useSkill() { System.out.println("강타!"); }
}
class Mage extends GameCharacter {
@Override
void useSkill() { System.out.println("파이어볼!"); }
}
인터페이스를 선택해야 하는 경우
관련이 없는 클래스들끼리 관계를 맺어줄 때, 특정 데이터 타입의 동작을 지정하려고 하지만 해당 동작을 누가 구현하는지 중요하지 않을 경우 인터페이스를 사용합니다. Thepublic
인터페이스 선택 기준:
- 서로 관련 없는 클래스들에 공통 기능을 부여할 때
- 다중 상속이 필요할 때 (클래스는 하나만 상속 가능)
- API나 라이브러리 설계 시 구현 세부사항을 숨기고 싶을 때
- 구현체가 바뀌더라도 사용하는 쪽 코드는 그대로 두고 싶을 때 (DIP 원칙)
실제 예시 — 결제 시스템 설계:
java
// 결제 방식은 서로 다른 회사지만 같은 동작을 보장해야 함
interface Payable {
boolean pay(int amount);
void refund(int amount);
}
// 관련 없는 클래스들이 동일한 계약을 따름
class KakaoPayment implements Payable {
@Override
public boolean pay(int amount) {
System.out.println("카카오페이 결제: " + amount + "원");
return true;
}
@Override
public void refund(int amount) { System.out.println("카카오페이 환불"); }
}
class NaverPayment implements Payable {
@Override
public boolean pay(int amount) {
System.out.println("네이버페이 결제: " + amount + "원");
return true;
}
@Override
public void refund(int amount) { System.out.println("네이버페이 환불"); }
}
// 사용하는 쪽은 구현체가 뭔지 몰라도 됨 — 느슨한 결합
class ShoppingCart {
void checkout(Payable payment, int total) {
payment.pay(total); // KakaoPay든 NaverPay든 상관없음
}
}
이 설계에서 ShoppingCart는 KakaoPayment도, NaverPayment도 모릅니다. Payable 인터페이스만 알면 됩니다. 나중에 새로운 결제 수단이 생겨도 ShoppingCart 코드를 전혀 수정하지 않아도 됩니다. 이것이 인터페이스의 핵심 가치인 확장성과 유연성입니다.
Java 8 이후 — default 메서드가 경계를 흐리는 이슈
Java 8부터 인터페이스에 default 키워드로 구현 있는 메서드를 넣을 수 있게 되었습니다. 이 때문에 “이제 추상클래스 필요 없는 거 아닌가?”라는 의문이 생기기도 합니다.
java
interface Greeting {
// Java 8+ default 메서드
default void hello() {
System.out.println("안녕하세요!");
}
void goodbye(); // 여전히 추상 메서드
}
그러나 핵심적인 차이는 여전히 남아 있습니다. 인터페이스는 **상태(멤버 변수)**를 가질 수 없습니다. 객체의 상태를 함께 관리하고 공유해야 한다면 추상클래스가 유일한 선택입니다. 이펙티브 자바에서도 추상 클래스보다 인터페이스를 우선하라고 권고하지만, 상태를 공유해야 하는 경우에는 추상 클래스를 사용해야 합니다. Thepublic
6. 면접 답변 공식과 자주 나오는 심화 질문
면접 기본 답변 공식
면접에서 “인터페이스와 추상클래스의 차이를 설명해 주세요”라는 질문을 받았을 때 다음 구조로 답변하면 효과적입니다.
답변 구조: 정의 → 문법 차이 → 목적 차이 → 선택 기준
"인터페이스와 추상클래스는 모두 추상화를 구현하는 도구이지만
목적과 사용 방식에 차이가 있습니다.
추상클래스는 abstract 키워드로 선언하며 extends로 단일 상속합니다.
일반 메서드·멤버 변수·생성자를 가질 수 있어, 관련된 클래스들 간에
공통 코드와 상태를 공유하기 위해 사용합니다. 'is-a' 관계를 표현합니다.
인터페이스는 interface 키워드로 선언하며 implements로 다중 구현이 가능합니다.
메서드 선언만 포함하며(Java 8+는 default 허용), 관련 없는 클래스들에게
특정 동작을 보장하는 계약을 맺을 때 사용합니다. 'is able to' 관계를 표현합니다.
따라서 공통 필드와 코드를 공유해야 할 때는 추상클래스를,
유연한 확장과 다중 구현이 필요할 때는 인터페이스를 선택합니다."
자주 나오는 심화 질문 Q&A
Q1. 자바가 다중 상속을 클래스에서 지원하지 않는 이유는?
다이아몬드 문제(Diamond Problem) 때문입니다. A와 B 클래스가 같은 이름의 메서드를 가지고 있고, C가 A와 B를 동시에 상속하면 어떤 메서드를 따라야 할지 모호해집니다. 인터페이스는 추상 메서드만을 가지고 있기 때문에, 다중 상속하고 있는 인터페이스들에 동일한 메서드가 존재한다 하더라도 그 내부 구현은 정의되지 않았으니 문제가 없습니다. 구현체가 직접 정의하면 되기 때문입니다. Brokdam
Q2. 인터페이스의 default 메서드는 왜 생겼나?
이미 작성된 인터페이스에서 기능을 추가하려 할 때, default 메서드 없이는 구현체 클래스들이 전부 Override를 해야 합니다. 그러나 default 메서드에서 메서드 body가 정의되어 있다면 개별적으로 구현하는 과정 없이 하위 호환이 가능해집니다. Java 8에서 스트림·람다 API를 추가하면서 기존 Collection 같은 인터페이스에 메서드를 추가해야 했는데, 이때 기존 구현체들이 모두 깨지는 것을 막기 위해 도입됐습니다. Thepublic
Q3. 추상클래스보다 인터페이스를 우선 사용해야 한다는 말의 근거는?
인터페이스는 시스템의 확장성과 유지 보수성을 향상시키는 데 기여합니다. 인터페이스를 통해 정의된 API는 변경 없이 구현 클래스를 교체하거나 추가할 수 있기 때문입니다. 또한 추상 클래스의 경우 기존 클래스에 새로운 추상 클래스를 상속하도록 하기 어렵습니다. 상속 계층 구조에 한번 들어가면 변경이 어렵지만, 인터페이스는 기존 클래스에 자유롭게 추가할 수 있습니다. 이것이 이펙티브 자바(Effective Java)에서 “추상 클래스보다는 인터페이스를 우선하라”고 권고하는 핵심 이유입니다. Namu WikiDividend-nomad
Q4. Python에서는 인터페이스가 없는데 어떻게 처리하나?
파이썬은 덕 타이핑(Duck Typing)을 지원하기 때문에, 인터페이스를 명시적으로 정의하지 않아도 됩니다. 파이썬에서는 abc 모듈을 사용하여 추상 클래스를 정의할 수 있으며, 인터페이스는 파이썬에서 명시적으로 지원하지 않지만 추상 클래스를 통해 유사하게 구현할 수 있습니다. Namu Wiki
결론
인터페이스 추상클래스 차이를 한 줄로 압축하면 이렇습니다. 추상클래스는 “이것들은 모두 ~이다”라는 계층 관계에서 공통 코드와 상태를 공유하기 위해 쓰고, 인터페이스는 “이것들은 모두 ~할 수 있다”라는 능력 계약을 서로 관련 없는 클래스들에게 강제하기 위해 씁니다. 코드를 공유해야 하면 추상클래스, 유연한 확장과 다중 구현이 필요하면 인터페이스가 정답입니다. 오늘 정리한 내용을 바탕으로 직접 코드를 작성해보면서 두 개념의 차이를 몸으로 익히는 것이 가장 효과적인 방법입니다.
ℹ️ 참고 안내 본 글의 코드 예시는 Java 11 기준으로 작성되었습니다. Java 버전에 따라 인터페이스 기능(default 메서드: Java 8+, private 메서드: Java 9+)이 다를 수 있습니다. 언어별(Python, C#, TypeScript 등) 추상화 구현 방식은 각 언어 공식 문서를 참고하시기 바랍니다.
답글 남기기