Java를 보면서 인터페이스와 추상 클래스 두가지가 너무 비슷한 것 같아서 차이가 뭔지 궁금했다.
추상 클래스
클래스 앞에 'abstract' 키워드를 사용하여 정의하며, 하나 이상의 추상 메서드를 가지고 있거나 abstract로 정의가 된 클래스
추상 클래스를 선언하여 상속을 위해서 하위 클래스에서 반드시 구현되도록 강제하는 클래스
추상 클래스는 추상 메서드를 포함하는 것을 제외하면 나머지 클래스와 모든 점이 동일하다.
즉, 생성자, 필드(맴버 변수), 일반 메서드가 포함 가능하다.
'is-a' 관계를 전제한 공통 특성 및 행동의 재사용과 확장에 적합하다.
Cat is a Animal
Dog is a Animal
특징
- 추상 클래스는 인스턴스, 즉 객체를 만들 수 없는 클래스이다. new 키워드로 객체 생성이 불가능하다.
- 추상 클래스는 일반 클래스 상속과 동일하게 extends 키워드를 사용한다.
- 추상 메서드는 (추상 클래스를 상속 받는) 하위 클래스에서 추상 메서드의 구현을 강제해야 한다.(Override)
- 추상 메서드를 포함하는 클래스는 반드시 추상 클래스여야 한다.
- 다중 상속이 불가능하다.
인터페이스
'interface'라는 키워드를 사용해 정의하며, 오직 추상 메서드와 상수(static final)만을 가지고 있는 것
추상 클래스와 마찬가지로 인터페이스 또한 선언되어 있는 추상 메서드를 구현(implements)하는 클래스에서 반드시 구현하도록 강제하고 있다.
'can-do' 계약으로 다양한 타입에 동일 동작을 강제한다.(수행 가능하다.)
Drone can do Flyable
Bird can do Flyable
특징
- 인터페이스의 모든 맴버 변수는 public static final 이어야 하며, 이를 생략할 수 있다.
- 인터페이스의 모든 메서드는 public abstract 이어야 하며, 이를 생략할 수 있다.
- Java 8 부터는 static, default method를 사용할 수 있다.
- 인터페이스는 상속 키워드로 'implements'를 사용한다.
- 다중 상속이 가능하다.
추상 클래스 예제 코드
중복된 맴버 변수와 메서드를 통합한다.
abstract class Animal {
protected final String name;
public Animal(String name) {
this.name = name;
}
public void breathe() {
System.out.println(name + " 숨쉰다");
}
public void eat() {
System.out.println(name + " 먹는다");
}
//추상 클래스
public abstract void cry();
}
class Dog extends Animal {
public Dog(String name) { super(name); }
@Override
public void cry() {
System.out.println(name + ": 멍멍!");
}
}
class Cat extends Animal {
public Cat(String name) { super(name); }
@Override
public void cry() {
System.out.println(name + ": 야옹~");
}
}
public class AbstractDemo {
public static void main(String[] args) {
Animal dog = new Dog("바둑이");
Animal cat = new Cat("나비");
dog.breathe();
dog.eat();
dog.cry();
cat.breathe();
cat.eat();
cat.cry();
}
}
위에서 설명 했던 대로 추상 클래스는 공통적인 기능을 구현할 수 있는 메서드(공통 메서드)와 함께 추상 메서드를 포함할 수 있다.
이 구조는 기본적인 행동 방식을 제공하면서, 특정 행동은 하위 클래스가 제공하도록 강제한다.
하지만 만약에 interface로 구현을 한다고 하면,
interface Animal {
void breathe();
void eat();
void cry();
}
class Dog implements Animal {
String name;
@Override
public void breathe() {
System.out.println(name + "가 숨을 쉰다.");
}
@Override
public void eat() {
System.out.println(name + "가 먹는다.");
}
@Override
public void cry() {
System.out.println(name + ": 멍멍!");
}
}
class Cat implements Animal {
String name;
@Override
public void breathe() {
System.out.println(name + "가 숨을 쉰다.");
}
@Override
public void eat() {
System.out.println(name + "가 먹는다.");
}
@Override
public void cry() {
System.out.println(name + ": 야옹~");
}
}
public class AbstractDemo {
public static void main(String[] args) {
Animal dog = new Dog("바둑이");
Animal cat = new Cat("나비");
dog.breathe();
dog.eat();
dog.cry();
cat.breathe();
cat.eat();
cat.cry();
}
}
인터페이스에는 상수와 추상 메서드만 존재할 수 있다.
즉, 맴버 변수와 일반 메서드를 사용할 수 없기 때문에 공통 로직을 계속 작성해야한다. 동물이 100마리면 100개의 클래스에 공통 로직을 작성해야한다는 의미이다.
추상 클래스를 사용하면, 정의된 공통 메서드 로직을 바꾸면 상속받은 나머지 하위 클래스의 공통 메서드도 로직이 변경되게 된다.(100번을 1번으로 변경할 수 있다.)
그렇기 때문에, 공통적인(중복) 기능을 구현하면서, 일부 메서드는 하위 클래스에서만 구현하도록 강제해야할 때, 인터페이스가 아닌 추상 클래스를 사용해야한다고 할 수 있다.
인터페이스 예제 코드
interface Flyable {
void fly();
// Java 8+: 기본 제공 가능
default void takeOff() {
System.out.println("이륙한다");
}
// Java 8+: 유틸리티
static void checkWinds() {
System.out.println("바람 세기 점검 중...");
}
}
interface Chargeable {
void charge();
}
class Drone implements Flyable, Chargeable {
@Override
public void fly() {
System.out.println("드론이 비행 중");
}
@Override
public void charge() {
System.out.println("드론 충전 중");
}
}
class Bird implements Flyable {
@Override
public void fly() {
System.out.println("새가 날갯짓한다");
}
// 필요하면 기본 동작 재정의 가능
@Override
public void takeOff() {
System.out.println("새가 도약하여 이륙한다");
}
}
public class InterfaceDemo {
public static void main(String[] args) {
Flyable.checkWinds(); // static 메서드
Flyable drone = new Drone();
drone.takeOff(); // default 메서드
drone.fly();
Flyable bird = new Bird();
bird.takeOff(); // 오버라이드된 default
bird.fly();
// 다중 구현 예시
Chargeable c = new Drone();
c.charge();
}
}
위 코드에서는 Drone, Bird와 같은 경우에 Flyable이라는 인터페이스를 구현하고 있다. Flyable 인터페이스는 Bird, Drone에 대해 공통 인터페이스를 제공한다.
위 코드도 '추상 클래스를 사용해도 괜찮겠는데?'라는 생각을 할 수 있다.
추상 클래스를 사용한다면 드론과 새가 "난다"라는 공통의 개념을 공유하지만 완전히 다른 객체의 유형이다.
예를 들어, 드론과 새는 날 수 있는 공통점이 존재하지만 드론은 무기(Weapon) 관련 클래스일 수 있고, 새는 동물 관련 클래스일 수 있다.
두가지 공통점은 "난다"는 공통적은 특성 및 계약이지만 논리적으로 같은 개념이 아니다.
정리하면, 추상 클래스의 상속은 'is-a'관계를 의미하고 인터페이스를 'can-do' 관계를 의미한다.
'Development > Java' 카테고리의 다른 글
| [Java] Static이란? (0) | 2026.01.02 |
|---|---|
| [Java] 다형성(Polymorphism) (0) | 2025.11.13 |
| [Java] equals(), == 연산 null 비교 (0) | 2025.06.20 |
| [Java] ==와 equals()의 차이 (2) | 2025.06.16 |
| [Java] String compareTo() 메서드 (2) | 2025.06.15 |