반응형
Notice
Recent Posts
Recent Comments
Link
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

디케이

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

Java

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

디케이형 2021. 2. 27. 11:54
반응형

추상클래스 인터페이스 왜... 사용할까?

우리는 추상클래스와 인터페이스에 대해서 알고 있냐고 누가 물어본다면 알고 있다고 대답을 하고있습니다. 그런데 이론적인 내용 말고 정작 "왜 사용하나요?", "차이점이 뭔가요?", "추상클래스를 사용할지 인터페이스를 사용할지 어떻게 정하나요?" 이런 실무적인 질문이 들어오면 시원하게 대답하기가 생각보다 힘들겁니다. 힘드셨기 때문에 지금 이 글을 읽고 계실거라고 생각합니다. 저도 마찬가지였고 단순히 추상클래스와 인터페이스가 무엇인지 보다 왜 사용하는지가 궁금하셨다면 아래 제 생각들을 천천히 읽어주시면 도움이 될 수 있을것이라 생각합니다.

 

 

일단 기본적으로 추상클래스와 인터페이스가 무엇인지 알아봅시다.

추상클래스란?

추상클래스는 일반 클래스와 별 다를 것이 없습니다. 단지, 추상 메서드를 선언하여 상속을 통해서 자손 클래스에서 완성하도록 유도하는 클래스입니다. 그래서 미완성 설계도라고도 표현합니다. 상속을 위한 클래스이기 때문에 따로 객체를 생성할 수 없습니다.

class 앞에 "abstract" 예약어를 사용하여 상속을 통해서 구현해야한다는 것을 알려주고 선언부만 작성하는 추상메서드를 선언할 수 있습니다.

1
2
3
4
abstract class 클래스이름 {
    ...
    public abstract void 메서드이름();
}

 

인터페이스란?

추상클래스가 미완성 설계도라면 인터페이스는 기본 설계도라고 할 수 있습니다. 인터페이스도 추상클래스처럼 다른 클래스를 작성하는데 도움을 주는 목적으로 작성하고 클래스와 다르게 다중상속(구현)이 가능합니다.

1
2
3
4
interface 인터페이스이름 {
    public static final 상수이름 = 값;
    public abstract void 메서드이름();
}

 

추상클래스 VS 인터페이스 차이점

추상클래스와 인터페이스의 공통점은 추상메서드를 사용할 수 있다는 것입니다. 그럼 왜 굳이 2가지로 나눠서 사용할까요? 추상클래스와 인터페이스의 기능들을 살펴보면 추상클래스가 인터페이스의 역할을 다 할 수 있는데 왜 굳이 인터페이스라는게 있는 걸까요?

이론적인 차이점을 다 제외하고 두개로 나눠서 사용하는 가장 큰 차이점은 사용용도라고 생각합니다.

 

1. 사용의도 차이점

추상클래스는 IS - A "~이다".

인터페이스는 HAS - A "~을 할 수 있는".

이렇게 구분하는 이유는 다중상속의 가능 여부에 따라 용도를 정한 것 같습니다. 자바의 특성상 한개의 클래스만 상속이 가능하여 해당 클래스의 구분을 추상클래스 상속을 통해 해결하고, 할 수 있는 기능들을 인터페이스로 구현합니다.

이렇게 글로 표현하여 이해가 어려울 수 있습니다. 아래 예제를 통해 더 자세하게 설명하겠습니다.

 

2. 공통된 기능 사용 여부

만약 모든 클래스가 인터페이스를 사용해서 기본 틀을 구성한다면... 공통으로 필요한 기능들도 모든 클래스에서 오버라이딩 하여 재정의 해야하는 번거로움이 있습니다. 이렇게 공통된 기능이 필요하다면 추상클래스를 이용해서 일반 메서드를 작성하여 자식 클래스에서 사용할 수 있도록 하면 된다. 어!? 그러면 그냥 추상클래스만 사용하면 되는 거 아닌가요? 위에서 얘기 했듯이 자바는 하나의 클래스만 상속이 가능합니다. 만약 각각 다른 추상클래스를 상속하는데 공통된 기능이 필요하다면? 해당 기능을 인터페이스로 작성해서 구현하는게 편하겠죠?

 

아무래도 글로만 설명하는데는 한계가 있는 것 같습니다. 아래 예제를 통해 어떻게 언제 무엇을 사용해야할지 알아 보도록 합시다.

추상클래스 인터페이스 예제 (생명체)

 

 

위와 같은 관계를 갖는 예제를 만들어 보겠습니다. 인간과 동물은 생명체를 상속하고 각 생명체들은 구분에 따라 인간과 동물을 상속합니다. 그리고 각각 할 수 있는 기능들을 인터페이스로 구현했습니다.

 

Creature 추상클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public abstract class Creature {
    private int x;
    private int y;
    private int age;
    
    public Creature(int x, int y, int age) {
        this.age = age;
        this.x = x;
        this.y = y;
    }
    
    public void age() {
        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;
    }
    
    public abstract void attack();
    public abstract void printInfo();
    
    @Override
    public String toString() {
        return "x : " + x + ", y : " + y + ", age : " + age;
    }
}
 

기본적으로 생명체가 갖는 요소로 위치 x, y그리고 age 나이가 필요하다고 생각하여 선언했고 생성자가 만들어 질때 이 3가지 요소는 받아서 넣을 수 있도록 했습니다. toString 메서드는 나중에 출력은 간편하게 하기 위해서 오버라이딩하여 사용했습니다.

 

1
2
3
4
5
6
7
public void age() {
    age++;
}
 
public void move(int xDistance) {
    x += xDistance;
}
 

생명체라면 나이를 먹고 x좌표상으로 이동 할 수 있는 부분이 공통 적인 기능이기 때문에 하위 클래스에서 상속할 수 있도록 일반 메서드로 구현했습니다.

 

1
2
public abstract void attack();
public abstract void printInfo();

추상메서드로는 공격하는 기능과 정보를 출력하는 기능을 선언했습니다. 모든 생명체에 필요한 기능이지만 각각 생명체에 따라 다른 기능으로 구현을 해야하기 때문에 위 두 메서드는 추상메서드로 선언하여 하위클래스에서 처리하도록 한 것입니다.

 

Animal 추상클래스

1
2
3
4
5
6
7
8
9
10
11
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 추상클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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("사람은 말을 할 수 있다.");
    }
}
 

인간 클래스도 마찬가지로 attack 추상메서드는 구현해주고, printInfo 추상메서드는 아래로 위임한 것입니다. 인간 클래스는 동물 클래스와 다르게 Talkable 인터페이스를 구현했습니다.  예상할 수 있듯이 Talkable 인터페이스에서는 talk메서드가 추상메서드로 있겠죠? 추상클래스도 인터페이스를 구현할 수 있는 것을 보면 정말 일반클래스와 크게 차이가 없다는 것을 알 수 있습니다.

 

Talkable 인터페이스

1
2
3
public interface Talkable {
    abstract void talk();
}

Talkable 인터페이스에서는 크게 다룰 것 없이 추상메서드를 하나만 선언 했습니다. 제가 작성하는 인터페이스들의 명명 규칙을 보시면 아시겠지만 ~able로 끝나는 인터페이스들이 많습니다. 모든 인터페이스가 다 그런 것은 아니지만 "~를 할 수 있는" 클래스라는 것은 명시해주기 위해 사용됩니다. ex) Parsable, Comparable...

 

Flyable 인터페이스

1
2
3
4
public interface Flyable {
    void fly(int yDistance);
    void flyMove(int xDistance, int yDistance);
}
 

다음은 새 종류의 동물 클래스에 구현시킬 Flyable 인터페이스입니다. 다른 동물들과는 다르게 y행으로 위로 올라갈 수 있도록 하는 메서드들을 선언했습니다.

 

Pigeon 일반클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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());
    }
}
 

비둘기 클래스는 일반 클래스입니다. 동물 클래스를 상속하고 날수 있는 동물이기에 Flyable 인터페이스를 구현해주고 해당 메서드들을 구현 해 주었습니다. printInfo 메서드를 보시면 이전에 Creature클래서의 추상메서드를 오버라이딩 해주었죠?

 

Swimable 인터페이스

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

Swimable 인터페이스입니다. 이 인터페이스는 차이점에서 2. 공통된 기능 사용여부와 관련된 인터페이스입니다. 저는 앞으로 거북이와 케빈이라는 클래스를 작성할겁니다. 거북이는 동물이고 케빈은 사람입니다. 그런데 두 생명체 모두 수영을 할 수 있습니다. 이런경우 생명체 클래스에다가 swinDown 추상메서드를 만들어 주어야하나? 아니면 각각 동물과 사람 클래스에 추상메서드를 만들어 주어야하나? 음... 하지만 동물이나 사람중에서 수영을 못하는 사람이 있을 수도 있다!? 바로 이런 경우에 인터페이스로 다로 선언을 해줘서 각각 수영을 할 수 있는 클래스에 구현시켜줘서 만들어 주면 가독성도 좋고 유지보수하는 측면에서도 뛰어나겠죠??

 

Turtle 일반클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Turtle extends Animal implements Swimable{
    public Turtle(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());
    }
}
 

거북이 클래스에서 아래로 수영할 수 있는 기능을 재정의 해주고

 

Kevin 일반클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Kevin extends Human implements Programmer, Swimable{
    public Kevin(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("Kevin -> " + toString());
    }
}
 

Kevin클래스에서도 오버라이딩하여 재정의 해줄 수 있다. 케빈 같은 경우는 y값이 -10이하로 내려가면 죽을 수도 있다는 것을 알려주는 또 다른 기능으로 재정의 했습니다. 바로 이런 부분에서는 추상클래스 말고 인터페이스를 사용하는 것이 좋겠죠??

그리고 또다른 인터페이스의 장점! 바로 다중구현! Kevin은 수영도 할 수 있고 코딩도 할 수 있습니다. 그래서 Programmer와 Swimable 인터페이스를 모두 구현하고 필요한 메서드를 재정의해서 사용하면 됩니다.

 

Programmer 인터페이스

1
2
3
public interface Programmer {
    void coding();
}

 

Main 실행클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class Main {
    public static void main(String[] args) {
        Pigeon p = new Pigeon(5,10,14);
        p.printInfo();
        p.age();
        p.move(100);
        p.printInfo();
        p.fly(5);
        p.printInfo();
        p.flyMove(1020);
        p.printInfo();
        p.attack();
        System.out.println();
        
        Kevin kev = new Kevin(0035);
        kev.printInfo();
        kev.age();
        kev.move(10);
        kev.printInfo();
        kev.attack();
        kev.coding();
        kev.swimDown(20);
        kev.printInfo();
        kev.talk();
        System.out.println();
        
        Turtle tur = new Turtle(100-1095);
        tur.printInfo();
        tur.age();
        tur.move(-100);
        tur.printInfo();
        tur.attack();
        tur.swimDown(1000);
        tur.printInfo();
    }
}
 

설계한 대로 코드를 작성했으니... 이제 확인 해봐야겠죠?

실행결과

 

 

위 예시처럼 직접 사용해서 다른 예시를 만들어 주면 인터페이스와 추상클래스의 차이점과 상황별 어떤 것을 사용해야할지 감이 올것 같아요. 저도 나름대로 공부하고 실습을 통해 추상클래스와 인터페이스에 대한 정리를 해봤는데 맞고 틀린 부분들도 분명 존재할 것이라고 생가각합니다. 정정 및 위 내용에 추가하고 싶은 것이 있다면 댓글 꼭 남겨주세요!!

 

정리

추상클래스 사용 시기 : 상속 관계를 쭉 타고 올라갔을때 같은 조상클래스를 상속하는데 기능까지 완변히 똑같은 기능이 필요한 경우

(ex. attack, printInfo)

인터페이스 사용 시기 : 상속 관계를 쭉 타고 올라갔을때 다른 조상클래스를 상속하는데 같은 기능이 필요할 경우 인터페이스 사용

(ex. Swimable)

 

 

반응형