추상클래스와 인터페이스

2025. 2. 25. 17:42Java/객체지향

1. 추상클래스 (Abstract Class)

1-1. 추상메소드 

// 추상 메소드의 구현: 리턴타입 앞에 abstract키워드 붙이기
public abstract void method();     // 추상메소드는 반드시 끝에 세미콜론을 붙여 주어야 한다.
  • 추상 메소드: 선언부만 작성하고 구현부 {}는 작성하지 않은 채로 남겨둔 메소드 
  • 설계만 해놓고 실제 수행될 내용은 작성하지 않았기 때문에 미완성 메소드이다.
  • 메소드를 미완성 상태로 남겨 놓는 이유는  메소드의 내용이 소속받는 클래스에 따라 달라질 수 있기 때문에 클래스에서는 선언부만 작성하고, 실제 내용은 상속받는 클래스에서 구현하도록 비워두는 것이다.
  • 자식클래스에서 클래스 내용을 구현하지 않으면 인스턴스가 생성되지 않으므로 내용 구현에 강제성을 줄 수 있다

1-2. 추상클래스

//class 앞에 abstract 키워드 붙이기 
//추상 메소드를 하나라도 포함하면 반드시 추상 클래스가 된다.
abstract class Animal {
    abstract void makeSound();  // 추상 메서드 (구현 X)
    public void method(){}  	// 추상 클래스에는 추상메소드가 아닌 일반적인 필드, 메서드, 생성자도 존재 가능
}

//상속 
class Dog extends Animal {
    @Override
    void makeSound() {
        System.out.println("멍멍!"); // 반드시 오버라이딩해야 함
    }
}

public class Main {
    public static void main(String[] args) {
        Animal dog = new Dog();
        dog.makeSound(); // "멍멍!" 출력
    }
}
  • 추상 클래스는 하위 클래스들의 공통점들을 모아 추상화하여 만든 클래스
    • 추상화: 클래스 간의 공통점을 찾아내서 공통의 부모을 만드는 작업
    • 구체화: 상속을 통해 구체적인 클래스를 구현하고 확장하는 작업
    • 클래스A, B, C가 3가지의 공통점이 있으면 그것을 뽑아내서 추상클래스 X를 만든다.
    • 공통점은 추상클래스 X를 상속받고 그 외의 부분은 자신의 클래스에 맞게 구현하여 완성해야만 한다.
  • 추상 클래스는 추상 메소드를 포함하고 있는 불완전한 클래스
    • 추상클래스로는 스스로 자신의 생성자를 활용한 인스턴스 생성이 불가능. 
    • 따라서 상속을 활용해 하위 클래스 타입의 인스턴스를 이용해서 인스턴스를 생성해야 한다.

1-3. 추상 클래스 사용 이유

  • 공통 멤버 통합으로 중복 제거
    필수 기능을 정의해 일관된 인터페이스(동일 기능)를 제공함에 있어 도움이 된다.
  • 구현의 강제성을 통한 기능 보장
    추상 메소드를 포함한 추상 클래스는 추상 메소드를 통해 자식 클래스에 오버라이딩에 대한 강제성을 부여할 수 있다. (추상 클래스를 상속받는 자식 클래스는 반드시 추상 메소드를 오버라이딩 해야 한다.)
  • 규격에 맞는 설계 구현 
    실제 프로젝트에서 어플리케이션 아키텍쳐가 설계해 놓은 추상 클래스를 상속받으면, 개발자는 프로젝트에서 필요하고 공통적으로 들어가야하는 필드와 메서드를 오버라이딩해서 큰 설계를 생각할 필요 없이 구현만 하면 된다. 이렇게 하면 초기 설계 시간이 절약되고, 구현에만 집중할 수 있게 된다는 장점이 있다.

2. 인터페이스 (Interface)

2-1. 인터페이스

  • 추상 메소드와 상수 필드만 가질 수 있는 클래스의 변형체 (추상 메소드의 집합)
  • 인터페이스 또한 일종의 추상클래스이며 추상클래스보다 추상화정도가 높다.
  • 추상클래스는 추상 메소드를 하나라도 가지고있으면 추상클래스이고 일반 메소드와 멤버변수를 가질 수 있지만,
    인터페이스는 오직 추상 메소드와 상수만을 멤버로 가질 수 있고 그 외에는 다른 어떠한 요소도 허용하지 않는다.
  • 추상클래스가 '미완성 설계도' 라면, 인터페이스는 '기본 설계도' 라고 할 수 있다.

2-2. 인터페이스의 작성 - interface 키워드 

  • class 대신 interface라는 키워드를 사용하여 선언 
  • 모든 멤버변수는 상수 - public static final이어야 하며, 이를 생략할 수 있다.
  • 모든  메소드는 추상메소드 - public abstract이어야 하며, 이를 생략할 수 있다.
    • 단, Java 8 이후부터 default 메서드와 static 메서드도 가질 수 있음
interface Animal {
    // 모든 필드는 public static final만 가능하다.
    public static final PI = 3.1415;
    
    // public static final을 생략해도 자동으로 작성된다.(생략 가능)
    PI2 = 3.14;

    // 기본적으로 메소드는 public abstract여야 한다.
    public abstract void makeSound();
    
    // public abstract를 생략해도 자동으로 작성된다.(생략 가능)
    //void makeSound(); 
}

 

2-3. 인터페이스의 구현 - implements 키워드 

  • extends 대신 implementss라는 키워드를 사용하여 구현 
  • 인터페이스는 인터페이스로부터만 상속받을 수 있음 
  • 클래스와 달리 다중구현이 가능하다. class Dog implements Animal, Animal2, ..
  • 인터페이스도 추상클래스처럼 당연히 그 자체로는 인스턴스를 생성할 수 없다.
    추상클래스가 상속을 통해 추상 메소드를 완성하는 것 처럼, 인터페이스도 자신에 정의된 추상 메소드의 몸통을 만들어주는 '클래스'를 작성해야한다.
class Dog implements Animal { // 인터페이스 구현
    @Override //오버라이딩 필수 
    public void makeSound() {
        System.out.println("멍멍!");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal dog = new Dog();
        dog.makeSound(); // "멍멍!" 출력
    }
}

 

2-4. 인터페이스 사용 이유

  • 인터페이스를 사용하면 상속에 얽매이지 않고 더 유연한 객체지향 설계가 가능해진다. 
  • 또한 자바의 단일 상속이라는 단점을 어느 정도 극복하기 위해 사용되기도 하며 모든 클래스는 하나의 부모 클래스 외에도 여러 개의 인터페이스를 구현할 수 있다.
  • 표준화가 가능 -> 개발시간 단축 
    인터페이스는 공유를 목적으로 하는 상수를 기반으로 모든 기능을 공통화해서 강제성을 부여할 목적으로 만들어 졌다. (공통된 인터페이스)

 

3. 추상 클래스 vs 인터페이스 차이점 정리

 

구분 추상 클래스 인터페이스
사용 목적 공통 기능을 가진 클래스들의 상속을 위한 베이스 클래스 특정 기능을 정의하고 여러 클래스에서 구현할 때
자체 인스턴스 생성 불가 불가
다형성 적용시 상위타입 활용 가능 유무 가능 가능 
상속 키워드 extends implements
상속 방식 단일 상속만 가능 다중 구현 가능
메서드 추상 메서드 + 일반 메서드

기본적으로 추상 메서드만
(Java 8부터 default/static 메서드 가능)
abstract 키워드 명시 명시적 묵시적 
필드 (변수) 인스턴스 변수 가질 수 있음 public static final 상수만 가짐
생성자 생성자 가질 수 있음 생성자 없음

 

추상 클래스 사용

  • "is-a" 관계일 때 (예: Animal → Dog)
  • 공통적으로 필요한 필드나 메서드가 있을 때
  • 기본 구현을 포함한 클래스를 만들고 싶을 때

인터페이스 사용

  • "can-do" 관계일 때 (예: Flyable → Bird, Airplane)
  • 여러 클래스에서 공통 기능을 제공해야 할 때 (구현 방식이나 대상에 대해 추상화)
  • 다중 구현을 활용하고 싶을 때

 

 

4. 인터페이스를 사용하면 유연성이 생기는 이유

4-1. 상속만 이용하는 경우의 문제점 (강한 결합)

// 추상 클래스 (조상 클래스)
abstract class Creature { 
	abstract void swimming(); // 수영 동작을 하는 추상 메소드
}

// 추상 클래스 (부모 클래스)
abstract class Animal extends Creature { }
abstract class Fish extends Creature { }

// 자식 클래스
class Parrot extends Animal {
	// 앵무새는 수영을 할수 없지만 상속 관계로 인해 강제적으로 메소드를 구현해야하는 사태가 일어난다.
	void swimming() {} 
}
class Tiger extends Animal {
	// 호랑이는 수영을 할수 없지만 상속 관계로 인해 강제적으로 메소드를 구현해야하는 사태가 일어난다.
	void swimming() {}
}
class People extends Animal {
	void swimming() { //... } 
}
class Whale extends Fish {
	void swimming() { // ... } 
}

 

  • Creature라는 추상 클래스에 추상 메소드를 추가하면, 곧 이를 상속하는 모든 자손/자식 클래스에서 반드시 메소드를 구체화 해야한다는 규칙 때문에 실제로 수영을 못하는 호랑이(Tiger)와 앵무새(Parrot) 클래스에서도 메소드를 구현해야 하는 강제성이 생기게 된다.
  • 물론 메소드를 선언하기만 하고 빈칸으로 놔두면 되기는 하지만, 이는 객체 지향 설계에 위반될 뿐만 아니라 나중에 유지보수 면에서도 마이너스 적인 효과가 된다.

 

4-2. 인터페이스를 활용하여 유연하게 객체지향 설계 (약한 결합)

  • 인터페이스는 상속에 얽매히지 않는다. 인터페이스에 추상 메소드를 선언하고 이를 구현(implements) 하면서 자유로운 타입 묶음을 통해 추상화를 이루게 한다.
  • 날아 다니는 동작 메서드나, 말하는 동작 메서드를 각각 인터페이스마다 분리하여 선언하고 이를 각 자식 클래스에 자유롭게 상속시킴으로써 보다 구조화된 객체 지향 설계를 추구 할 수 있는 것이다.
//추상화 클래스로 상속 
abstract class Creature { } //부모 클래스를 추상클래스로 선언
abstract class Animal extends Creature { } //자식 클래스가 이를 상속 
abstract class Fish extends Creature { }

//인터페이스로 기능 추상화 
interface Flyable {
    void flying();
}

interface Talkable {
    void talking();
}

interface Swimmable {
    void swimming();
}

//인터페이스를 통해 상속 계층에 얽매이지 않고 필요한 기능만 조합해서 구현 가능 
class Tiger extends Animal { }

class Parrot extends Animal implements Talkable{
    @Override
    public void talking() {}
}

// 필요에 따라 적재적소에 다중으로 여러개 추가(구현)이 가능함
class People extends Animal implements Talkable, Swimmable{ 
    @Override
    public void talking() {}
    @Override
    public void swimming() {}
}

class Whale extends Fish implements Swimmable{
    @Override
    public void swimming() {}
}

 

4-3. 인터페이스 구현 장점 정리

1) 다중 구현이 가능 → 여러 역할을 가질 수 있음

  • 자바는 단일 상속만 가능하지만, 인터페이스는 여러 개를 동시에 구현할 수 있다.
    즉, 클래스가 특정 부모 클래스 하나에 종속되지 않고, 다양한 기능을 조합할 수 있다.
  • People 클래스는 Talkable(말하는 기능)과 Swimmable(헤엄치는 기능)을 동시에 가질 수 있다.
    만약 추상 클래스로 상속을 했다면, 하나의 클래스만 상속 가능하기 때문에 이런 유연성이 떨어진다. 

2) 특정 기능(행동)을 분리할 수 있음

  • 인터페이스는 행동(기능)을 정의하는 용도로 쓰이기 좋다,
  • 상속을 쓰면 부모-자식 관계가 형성되지만,
    인터페이스를 쓰면 특정 행동만 따로 분리해서 원하는 클래스에 붙일 수 있다.

3) 상속 트리가 복잡해지는 걸 방지할 수 있음

  • 상속을 많이 쓰면 부모-자식 관계가 깊어지고, 유지보수가 어려워질 수 있다.
    인터페이스를 활용하면 기능을 조합하는 방식으로 개발할 수 있어서 상속 트리가 단순해짐
  • 위의 예시처럼 Creature라는 추상 클래스에 Swimmable() 추상 메소드를 추가하면, 모든 인스턴스들이 무조건 수영을 할 수 있어야하는 문제가 생길 수 있다. 하지만 인터페이스를 사용하면 기능을 따로 조합할 수 있다.
  • 이렇게 하면 포유류/조류와 상관없이 원하는 동물에게 특정 기능만 부여할 수 있다.
    불필요한 상속 트리를 줄이고, 기능을 조합하는 방식으로 개발 가능!

 

4-4. 결론: 인터페이스가 객체지향 설계를 자유롭게 만든다. 

방식 특징 문제점 
상속 (extends) 공통된 기능을 재사용 단일 상속만 가능, 부모-자식 관계가 강하게 결합됨
인터페이스 (implements) 기능을 조합해서 다중 구현 가능 모든 메서드가 추상 메서드라 기본 구현이 필요

 

인터페이스를 사용하면 -> 상속 계층에 얽매이지 않고 필요한 기능만 조합해서 개발할 수 있다

  • 클래스 간의 강한 결합을 줄이고(느슨한 결합), 재사용성이 높아짐
  • 기능 단위로 설계가 가능해서, 객체지향 설계를 더 유연하게 할 수 있음
  • 유지보수가 쉬워지고, 새로운 기능을 추가해도 기존 구조를 크게 변경할 필요가 없다.

 

참고한 포스팅

 

☕ 인터페이스 vs 추상클래스 용도 차이점 - 완벽 이해

인터페이스 vs 추상클래스 비교 이 글을 찾아보는 독자분들은 아마도 이미 인터페이스와 추상클래스 개념을 학습한 뒤에 이 둘에 대하여 차이의 모호함 때문에 방문 했겠지만, 그래도 다시한번

inpa.tistory.com