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



image-20210815114036054

흔히 면접에서도 자주 물어보는 질문이다. 인터페이스와 추상클래스에 대해서 설명해보세요.

자바에서 흔히 추상클래스는 abstract를 사용하고 인터페이스는 interface를 사용하면 구현할 수 있다. 하지만 언제 추상클래스를 사용하고 언제 인터페이스를 사용해야할까?

개념적으로는 대충 알고있지만 실제로 코드를 짜려고 하면 조금 막막한 부분이 있었다. 본인도 정확히 개념을 알지 못하고서 개발을 해왔다. 이 글을 통해 추상클래스와 인터페이스에 대해 정확히 알아보도록 하자.


추상클래스

추상클래스는 일반 클래스와 다르지 않다. 추상클래스를 선언하여 상속을 통해서 자식 클래스에서 구현화하여 완성하도록 유도하는 클래스이다. 그래서 미완성 설계도 라고 표현한다.

기본적으로는 아래와 같이 사용한다.

public abstract class 클래스이름 {
  public abstract void 메서드이름();
}
  • 키워드 abstract를 붙여 표현한다.
    추상 메서드를 포함하지 않은 클래스에서도 abstract를 붙여 추상 클래스로 지정할 수 있다.
  • interface의 메서드와 같이 구현 부분은 없다.
  • abstract로 선언한 메서드를 자식 클래스에서 반드시 구현해야 한다.(오버라이딩)
  • 상속을 위한 클래스이기 때문에 필드에 따로 객체를 생성할 수 없다.
public class main {
  public static void main(String[] args) {
    //만약 추상 클래스를 new 연산자로 생성하면 오류가 발생한다.
    //(Cannot instantiate the type Creature 오류)
    Creature c = new Creature();
  }
}


인터페이스

인터페이스도 추상클래스처럼 다른 클래스를 작성하는데 도움을 주지만 클래스와 다르게 다중 상속이 가능하다. 추상클래스와는 다르게 기본설계도 라고들 표현한다.

기본적으로는 아래와 같이 사용한다.

interface 인터페이스 이름 {
  public static final 상수이름 = ;
  public abstract void 메서드이름();
}
  • 일반 메서드 또는 멤버 변수를 가질 수 있다.
  • 모든 멤버변수는 public static final이어야 하며 생략 가능하다.
  • 모든 메서드는 public abstract여야 하며 생략 가능하다.
    (단, JDK1.8부터 static 메서드와 default 메서드를 사용할 수 있다.)


추상클래스 vs 인터페이스

두 방식의 공통점은 추상 메서드를 사용할 수 있다는 것인데 왜 두가지로 나누어서 사용할까?

가장 큰 차이점은 사용용도 때문이다.

1. 사용용도

  • 추상클래스는 IS-A "~이다."
  • 인터페이스는 HAS-A "~를 할수있는"

다중 상속의 가능 여부에 따라 정해진 용도로 보인다. 자바의 특성상 클래스는 단 한개의 클래스만 상속이 가능하며(extends) 클래스의 속성을 추상클래스 상속을 통해 해결하고, 할 수 있는 기능들을 인터페이스로 구현한다.

2. 공통 기능 사용 여부

만약 모든 클래스가 인터페이스를 사용해서 기본 틀을 구성한다면 공통으로 필요한 기능들도 모든 클래스에서 오버라이딩 하여 재정의 해야한다.

그래서 공통된 기능이 필요하다면 추상클래스를 이용해 일반 메서드를 사용하여 자식클래스에서 사용할 수 있도록 한다. 하지만 다중 상속의 문제 때문에 각 클래스가 다른 추상클래스를 상속받을 수 있기 때문에 공통된 기능은 인터페이스로 분리하여 구현한다.


예제

image-20210815112844274

위와 같은 구성을 가지고 있는 예제를 만들어 보겠다.

HumanAnimalCreature를 상속하고 각 생명체 들은 구분에 따라 HumanAnimal을 상속한다. 그리고 할 수 있는 기능들은 인터페이스로 구현하였다.


Creature (추상)

public abstract class Creature {
  private int x;
  private int y;
  private int age;
  
  public Creature(int x, int y, int age) {
    this.x = x;
    this.y = y;
    this.age = age;
  }
  
  public abstract void attack();
  public abstract void printInfo();
  
  public void increaseAge() {
    age++;
  }
  
  public void move(int xDistance) {
    x += xDistance;
  }
  
  public int getX() {
    return x;
  }
  
  public void setX(int x) {
    this.x = x;
  }
  
  public int getY() {
    return y;
  }
  
  public void setY(int y) {
    this.y = y;
  }
  
  @Override
  public String toString() {
    return "x : " + x + ", y : " + y + ", age : " + age;
  }
}
  • 위치(x좌표, y좌표), 그리고 나이(age)는 기본적인 생명체가 가지고 있는 요소이기 때문에 필드로 구현했다.
  • 생명체라면 나이를 먹고(increaseAge) 움직이는 것(move)이 공통 기능이기 때문에 하위 클래스에서 간단하게 상속할 수 있도록 일반 메서드로 구현했다.

  • 공격하는 것(attack)은 모든 생명체에 필요한 기능이지만 각 생명체마다 다른 기능으로 구현해야하기 때문에 추상 메서드로 선언했다.


Animal (추상)

public abstract class Animal extends Creature {
  public Animal(int x, int y, int age) {
    super(x, y, age);
  }
  
  @Override
  public void attack() {
    System.out.println("몸통박치기!");
  }
}
  • 동물은 생명체이기 때문에 Creature 추상 클래스를 상속했다.
  • 몸을 사용하여 공격하기 때문에 attack() 메서드를 오버라이딩 했다.
  • printInfo()는 구현하지 않았다. 그 이유는 동물 클래스도 추상클래스이기 때문에 상위 클래스에서 선언한 추상메서드를 앞으로 동물 클래스를 상속할 클래스에게 위임하였다.


Human (추상)

public abstract class Human extends Creature implements Talkable {
  public Human(int x, int y, int age) {
    super(x, y, age);
  }
  
  @Override
  public void attack() {
    System.out.println("슉 슈숙. 슉.");
  }
  
  @Override
  public void talk() {
    System.out.println("안녕하세요. 반갑습니다.");
  }
}
  • 인간도 생명체이기 때문에 Creature 클래스를 상속하고, 특이하게도 Talkable 인터페이스를 구현했다. 예상할 수 있겠지만 Talkable 인터페이스에는 talk() 추상메서드가 있다.
  • 도구를 사용하여 공격하기 때문에 attack() 메서드를 오버라이딩 했다.


Talkable (인터페이스)

public interface Talkable {
  abstract void talk();
}
  • Talkable 인터페이스에서는 추상메서드를 하나만 선언했다.
  • 인터페이스들의 명명 규칙을 보면 알겠지만 ~able 로 끝나는 인터페이스들이 많은데 이는 “~를 할수있는” 을 직관적으로 명시해 주기 위해 사용된다.

Flyable (인터페이스)

public interface Flyable {
  void fly(int yDistance);
  void flyMove(int xDistance, int yDistance);
}
  • 다른 동물과는 다르게 y행을 통해 위로 올라갈 수 있도록 하는 메서드를 선언했다.


Pigeon (일반)

public class Pigeon extends Animal implements Flyable {
  public Pigeon(int x, int y, int age) {
    super(x, y, age);
  }
  
  @Override
  public void fly(int yDistance) {
    setY(getY() + yDistance);
  }
  
  @Override
  public void flyMove(int xDistance, int yDistance) {
    setY(getY() + yDistance);
    setX(getX() + xDistance);
  }
  
  @Override
  public void printInfo() {
    System.out.println("Pigeon -> " + toString());
  }
}
  • Animal 을 상속하고 날 수 있기 때문에 Flyable 인터페이스를 구현받아 구현해주었다.


Swimable (인터페이스)

public interface Swimable {
  void swimDown(int yDistance);
}

뒤쪽에서는 AlexDog 라는 일반 클래스를 작성 할 예정인데, Alex는 사람이고 Dog는 동물이다. 하지만 두 생명체 모두 수영을 할 수 있다. 이 경우 CreatureswimDown() 추상 메서드를 만들어 줘야 할까? 아니면 AnimalHuman에 추상 메서드를 만들어야 하나? 그런데 동물이나 사람중에 수영을 못하는 사람이 있을텐데?

이런 경우에 interface로 따로 구현해주어 수영을 할 수 있는 클래스에 구현받아 만들어 주면 가독성도 좋고 유지보수하는 측면에서도 뛰어나다.


Turtle (일반)

public class Turtle extends Animal implements Swimable {
  public Dog(int x, int y, int age) {
    super(x, y, age);
  }
  
  @Override
  public void swimDown(int yDistance) {
    setY(getY() - yDistance);
  }
  
  @Override
  public void printInfo() {
    System.out.println("Turtle -> " + toString());
  } 
}
  • 아래로 수영(swimDown)할 수 있는 기능을 재정의 해준다.


Alex (일반)

public class Alex extends Human implements Programmer, Swimable {
  public Alex(int x, int y, int age) {
    super(x, y, age);
  }
  
  @Override
  public void coding() {
    System.out.println("Hello World!");
  }
  
  @Override
  public void swimDown(int yDistance) {
    setY(getY() - yDistance);
    
    if(getY() < -10) {
      System.out.println("너무 깊이 들어가면 죽을 수 있어!");
    }
  }
  
  @Override
  public void printInfo() {
    System.out.println("Alex -> " + toString());
  }
}
  • ProgrammerSwimable 이 인터페이스이기 때문에 다중 상속이 가능한 것을 볼 수 있다. 덕분에 Alex 는 수영도 할 수 있고 코딩도 할 수 있게 되었다.
  • Alex의 경우 y 값이 -10 이하로 내려갈 경우 죽을 수 있다는것을 알려주도록 swimDown() 메서드를 다르게 재정의 하였다. 이런 부분에서는 추상클래스보다는 인터페이스가 좋다는 것을 알 수 있다.


Programmer (인터페이스)

public interface Programmer {
	void coding();
}

실행

이제 모든 설계가 끝났으니 한번 실행해 볼 수 있겠다!


Main

public class Main {
  public static void main(String[] args) {
    Pigeon pigeon = new Pigeon(5, 10, 2);
    pigeon.printInfo();
    pigeon.increaseAge();
    pigeon.move(50);
    pigeon.printInfo();
    pigeon.fly(3);
    pigeon.printInfo();
    pigeon.flyMove(10, 10);
    pigeon.printInfo();
    pigeon.attack();
    System.out.println();
    
    Alex alex = new Alex(0, 0, 25);
    alex.printInfo();
    alex.increaseAge();
    alex.move(10);
    alex.printInfo();
    alex.attack();
    alex.coding();
    alex.swimDown(20);
    alex.printInfo();
    System.out.println();
    
    Turtle turtle = new Turtle(20, -100, 100);
    turtle.printInfo();
    turtle.increaseAge();
    turtle.move(-50);
    turtle.printInfo();
    turtle.attack();
    turtle.swimDown(1000);
    turtle.printInfo();
  }
}


실행결과

Pigeon -> x : 5, y : 10, age : 2
Pigeon -> x : 55, y : 10, age : 3
Pigeon -> x : 55, y : 13, age : 3
Pigeon -> x : 65, y : 23, age : 3
몸통박치기!

Alex -> x : 0, y : 0, age : 25
Alex -> x : 10, y : 0, age : 26
슉 슈슉. 슉.
Hello World!
너무 깊이 들어가면 죽을 수 있어!
Alex -> x : 10, y : -20, age : 26
안녕하세요. 반갑습니다.

Turtle -> x : 20, y : -100, age : 100
Turtle -> x : -30, y : -100, age : 101
몸통박치기!
Turtle -> x : -30, y : -1100, age : 101

이러한 예제 구현을 통해 인터페이스와 추상클래스의 차이점과 어떨때 사용해야 하는지에 대한 감을 잡을 수 있었다.


정리

  • 추상클래스를 사용할 경우는

    상속관계를 타고 올라갔을 때 같은 부모클래스를 상속하는데 완전히 똑같은 기능이 필요한 경우

    (ex) attack(), printInfo()

  • 인터페이스를 사용할 경우는

    상속관계를 타고 올라갔을 때 다른 부모클래스를 상속하는데 같은 기능이 필요한 경우

    (ex) Swimable


출처

[JAVA] 추상클래스 VS 인터페이스 왜 사용할까? 차이점, 예제로 확인 :: 마이자몽

[01] 추상클래스와 인터페이스의 차이가 뭐죠?

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

인터페이스의 default method




© 2019. by mintheon

Powered by mintheon