Programming/Java

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

bu119 2023. 7. 13. 15:40
728x90
반응형

추상클래스

추상 메서드를 선언하여 상속을 통해서 자손 클래스에서 완성하도록 유도하는 클래스이다.

이러한 특성 때문에 미완성 설계도라고 표현한다.

 

추상클래스는 상속을 위한 클래스이기 때문에 따로 객체(인스턴스)를 생성할 수 없다.

상속을 통해서 자손클래스에 의해서만 완성될 수 있다.

추상 클래스는 추상 메소드를 0개 이상 가지고 있다는 것을 제외하고는 일반 클래스와 별 다를 것이 없다.

 

class 앞에 "abstract" 예약어를 붙여 해당 클래스가 추상 클래스임을 나타낸다.

또한 메서드도 "abstract" 예약어를 붙여 추상 메서드를 선언할 수 있다.

 

abstract class 클래스이름 {
    ...
    public abstract void 메서드이름();
}​

 

추상클래스의 특징

  • 추상 클래스는 추상 메서드를 가지지 않아도 괜찮다.
    • 다만, 추상 메서드를 하나라도 가진다면 그 클래스는 추상 클래스가 된다.
  • 추상 메서드를 선언했다면 자식 클래스는 해당 메서드를 반드시 구현하도록 강제된다.
  • JAVA는 다중 상속을 지원하지 않기 때문에 여러 개의 추상 클래스를 상속할 수 없다.
  • 추상 클래스는 static이나 final이 아닌 필드를 가질 수 있다.

 

  • 공통 구현을 상위 클래스에 뽑아내려고 사용한다.
  • 자신의 생성자로 객체 생성 불가능하다.
  • 하위 클래스를 참조하여 상위 클래스의 객체를 생성한다.
  • 하위 클래스를 제어하기 위해 사용한다.
  • abstract와 final 키워드를 동시에 표기할 수 없다.

 

abstract class 클래스{
	...
    public abstract void 메서드();
    
}

class 자식클래스 extends 클래스{
	
    @Override
    public void 메서드(){
    	... // 구현
    }
    
}

추상 클래스는 extends 키워드를 통해 상속이 가능하다.

 

추상클래스를 왜 사용하는가

맥락에 따라서 달라질 수 있는 기능들이 있을 때 추상 메소드로 만든다.

 

추상 메소드들의 공통된 내용을 상위 클래스로 뽑아서 만든 것이 추상 클래스이며  이것을 세부적으로 구현한 부분이 추상 메소드이다. 이 추상 메소드가 하나 이상 가지고 있는 것이 추상 클래스이고 이것들을 상속함으로써 자기가 가진 메소드들을 오버라이딩한다.

 

그래서  왜 사용하는가?

그것은 바로 코드의 중복과 유지보수의 편의성을 위해서다.

 

맥락에 따라서 달라질 수 있는 기능이란 무엇을 말할까, 그리고 여기에서 중복을 어떻게 제거하는가?

 

예시

쉽게 게임에서의 직업, 클래스를 예로 들어보자.

 

우리는 검사, 마법사라는 직업을 알고있다.
하지만 추후 마검사라는 직업을 추가한다고 할때. 이 직업들의 공통적인 특징인 '직업' 또는 클래스라는 내용으로 추상 클래스를 작성하고 그 하위에 검사와 마법사가 필요한 특징들을 추상 메소드로 구현한다.

검사 extends 클래스

무기를 휘두르는 메소드
체력이 높음
마법사 extends 클래스

무기를 휘두르는 메소드
마법을 사용할 수 있음
지능이 높음

 

마검사의 경우에는 검도 사용할 수 있으며 칼을 휘두르는 메소드 또한 사용할 수 있다. 그리고 마법을 사용할 수도 있을 것이다.

마검사 extends 클래스

무기를 휘두르는 메소드
마법을 사용할 수 있음
체력과 지능이 애매함

 

여기서 공통분모인 무기를 휘두른다는 메소드는 상위 클래스에 두어서 코드의 중복을 줄이고

그 이외에 마법을 사용한다든지 체력 또는 지능이 높다든지하는 경우는 클래스별 특징에 해당하므로

하위 클래스에서 추상메소드로 구현하도록 한다.

이런식으로 기능이나 어떤 출력에 예외를 둘 때와 같이 맥락에 따라서 달라질 때 추상 클래스와 메소드로 추상화,

분류하는 작업을 통해 중복을 줄이고 규모가 큰 프로젝트에서 유지보수를 용이하기 위해서 추상 클래스를 사용하게 된다.

 

인터페이스

인터페이스는 일종의 추상클래스이다.

 

인터페이스도 추상 클래스와 비슷하게 다른 클래스를 작성하는데 도움을 주는 목적으로 작성한다.

다만, 인터페이스는 추상클래스처럼 추상메서드를 갖지만 추상클래스 보다 추상화 정도가 높아서 추상클래스와는 달리 몸통(구현부)을 갖춘 일반 메서드 또는 멤버변수를 구성원으로 가질 수 없다.

 

추상클래스를 부분적으로만 완성된 '미완성 설계도'라고 한다면, 인터페이스는 구현된 것은 아 무것도 없고 밑그림만 그려져 있는 '기본 설계도'라고 할 수 있다. 

 

오직 추상메서드와 상수만을 멤버로 가질 수 있으며, 그 외의 어떠한 요소도 허용하지 않는다.

인터페이스는 인터페이스로부터만 상속받을 수 있으며,

클래스와는 달리 다중상속, 즉 여러개의 인터페이스로부터 상속을 받는 것이 가능 하다.

 

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

 

인터페이스의 특징

  • 모든 멤버 변수는 public static final 이어야 하며, 이를 생략할 수 있다.
  • 모든 메소드는 public abstract 이어야 하며, 이를 생략할 수 있다.
  • Java 8 부터는 static (정적), default method를 사용할 수 있다.
  • 다중상속이 가능하다.
  • 상속하는 집합간에는 연관관계가 존재하지 않을 수 있다.

 

  • 일종의 추상 클래스이다.
  • 모든 메서드가 구현된 부분이 없는 추상 메서드 형태로 이루어진 클래스이다.
  • Implements 키워드를 사용한다.
  • 상속의 관계가 없는 클래스간 서로 공통되는 로직을 구현하여 쓸 수 있도록한다.
  • Extends는 하나의 클래스만 상속 가능하나 Interface는 다중 상속이 가능하다.

 

 

디폴트 메서드

public interface Default{
    // 디폴트 메소드 : 실행 내용까지 작성 가능
    public default void 메서드(){
        ... // 구현 내용
    }
}

 

정적 메서드

public interface Static{   
    // 정적 메소드
    public static void 메서드(){
        ... // Static.메서드() 와 같이 호출 가능
    }
 }

 

인터페이스를 왜 사용하는가

기존 코드의 변경이 적고 확장 가능한 객체 지향적인 코딩을 하기 위해서 사용한다.

 

인터페이스는 협업시에 유용하게 사용할 수 있다.

 

예시

A,B,C 이렇게 엮여서 동작하는 클래스가 있다고 한다면,
A,B,C가 모두 클래스가 구현이 되어있어야 오류가 생기지 않고 작동이 된다.

하지만 협업시에 C는 따로 다른 팀에서 작업한다고 했을 때 C가 미완성된 상태에서 A와 B만 가지고 있다면,
이럴 때 인터페이스를 만들어 C를 가리키며 명시적으로 나타내기만 해준다면
C를 별도로 구현하지 않아도 작동이 되는 것이다.

 

즉, 일부로 결합도가 낮은 코드를 만들어 협업과 유지보수가 용이해지도록 만드는 것이다.

 

일반적으로 부모 클래스의 내부구현을 자식클래스들이 상속받아 공유할 필요가 있으면 추상클래스를 사용하고

공유할 내부 구현이 없다면 더 추상적인 인터페이스를 사용한다.

 

객체지향 설계에서 말하는 유연한 설계란 기존 코드의 변경이 적고 확장가능한 것을 말하기 때문에,

이를 실천하기 위해서는 추상적인 것을 의존하게 만드는 것으로 가능하다.

이는 의존 대상이 구체적이기 보단 추상적일 수록 더욱 좋다.

그렇기 때문에 추상클래스보다 더 추상적인 인터페이스는 객체지향적인 코딩을 하기에 꼭 필요한 유용한 방법이다.

 

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

  추상클래스 인터페이스
다중상속 불가능 가능
추상
메서드
0개 이상 전부
일반
메서드
가능 불가능. 다만 Java8부터는 디폴트, 정적 메서드 구현 가능
필드 일반 변수, 상수 모두 가능 상수(static final)만 가능
상속
키워드
extends implements
접근
제어자
제한 없음 public
공통점
  • 자기 자신이 new를 통해 객체(인스턴스)를 생성할 수 없으며,
    상속받은 자식만이 객체를 생성할 수 있다.
  • 상속받은 클래스는 반드시 추상 메서드를 구현해야 한다.

 

추상클래스와 인터페이스의 공통점은 추상메서드를 사용할 수 있다는 것이다.

기능적으로 봤을 때 둘 다 추상 메서드를 사용해 상속(구현) 받는 클래스가 추상 메서드를 구현하도록 강제하는 점이 같다. 또한, 추상클래스를 보면 다중 상속이 안된다는 점을 제외하고는 인터페이스의 역할을 충분히 할 수 있는 것처럼 보인다.

 

그럼 왜 굳이 2가지로 나눠서 사용할까?

추상클래스와 인터페이스의 기능들을 살펴보면

추상클래스가 인터페이스의 역할을 다 할 수 있는데 왜 굳이 인터페이스라는게 있는 걸까?

 

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

 

1. 사용의도

  • 추상 클래스 : 자식 클래스 is kind of 부모 클래스 
  • 인터페이스 : 자식 클래스 is able to 부모 인터페이스 

이렇게 구분하는 이유는 다중상속의 가능 여부에 따라 용도를 정한 것이다.

 

자바의 특성상 한개의 클래스만 상속이 가능하여 해당 클래스의 구분을 추상클래스 상속을 통해 해결하고, 할 수 있는 기능들을 인터페이스로 구현한다.

 

2. 공통된 기능 사용 여부

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

이렇게 공통된 기능이 필요하다면 추상클래스를 이용해서 일반 메서드를 작성하여 자식 클래스에서 사용할 수 있도록 하면 된다.

 

그러면 그냥 추상클래스만 사용하면 되는 거 아닌가?

위에서 얘기 했듯이 자바는 하나의 클래스만 상속이 가능하다.

만약 각각 다른 추상클래스를 상속하는데 공통된 기능이 필요하다면 해당 기능을 인터페이스로 작성해서 구현하는게 편하다.

 

예시

추상 클래스의 사용

명확한 계층 구조 추상화

 

[그림 1] 상속 관계

 

예를 들어 [그림 1]과 같은 상속관계의 클래스들이 있다고 하자.

 

  • 사람 is kind of 포유류
  • 포유류 is kind of 생물체
  • 조류 is kind of 생물체
  • ...

 

이와 같이 추상 클래스를 상속받는 자식 클래스는 is kind of 관계가 성립한다. 

추상 클래스는 extends 키워드를 사용하는데, 이는 마치 자신의 기능들을 하위 클래스로 확장시키는 느낌이다.

 

이처럼 클래스끼리의 명확한 계층 구조가 필요할 때 추상클래스를 사용할 수 있다. 

예를 들어 삼각형, 네모, 원은 도형이라는 계층 관계로 묶을 수 있다.

 

추상 클래스는 이를 상속받을 각 객체들의 공통점을 찾아 추상화시켜 놓은 것으로, 상속 관계를 타고 올라갔을 때 같은 부모 클래스를 상속하며 부모 클래스가 가진 기능들을 구현해야 할 경우 사용한다.

 

중복 멤버 통합

자식 클래스는 부모 클래스의 일반 멤버 변수도 상속받아 사용할 수 있다. 

예를 들어 [그림 1]에서 모든 생물체가 나이(int age)를 멤버 변수로 가진다고 하자.

만약 추상 클래스를 사용하지 않고 각각 따로 int age 멤버 변수를 가진다면 아래와 같이 정의될 것이다.

public class 포유류{
	int age;
    
    ... // 여러 메서드
}

public class 조류{
	int age;
    
    ... // 여러 메서드
}

 

반면 추상 클래스에 int age를 선언해 놓고 이를 상속받는다면, 중복되는 멤버 변수를 중복으로 선언하지 않아도 된다.

public abstract class 생물체{
	int age;
    ... // 여러 메서드
}

public class 포유류 extends 생물체 {
    ... // 여러 메서드
}

public class 조류  extends 생물체 {    
    ... // 여러 메서드
}

이처럼 자식 클래스 is kind of 부모 클래스의 관계가 성립하기 때문에 일반 멤버 변수의 통합도 가능하다.

 

추상 클래스를 이용하면 중복 멤버 변수의 통합이 가능하다. 인터페이스는 일반 멤버 변수를 선언하지 못하고 상수만 선언할 수 있기 때문에 이러한 중복 멤버 변수의 통합이 불가능하다. static final 상수를 선언한다면 각 클래스에서 값의 변경이 불가능할 것이다.

 

인터페이스의 사용

자유로운 관계 묶음

[그림 1]에서 박쥐와 독수리는 날 수 있고, 사람과 펭귄은 수영할 수 있다고 하자.

 

이때 박쥐와 독수리의 기능에 fly() 메서드를 추가하고 싶다면, 부모 클래스인 포유류나 조류에 fly() 메서드를 선언할 수 없다. 사람이나 펭귄은 날 수 없는데 메서드의 구현을 강제받기 때문이다.

 

어쩔 수 없이 박쥐와 독수리는 따로 fly() 메서드를 각자 구현해야 한다. 공통된 기능이 있음에도 추상화할 수 없는 것이다.

 

하지만 이때 인터페이스를 도입한다면 공통된 기능을 뽑아낼 수 있다.

 

[그림 2] 인터페이스

 

  • 박쥐 is able to 날기
  • 독수리 is able to 날기
  • 사람 is able to 수영
  • 펭귄 is able to 수영

 

위와 같이 is able to 관계에서는 인터페이스를 사용할 수 있다. 인터페이스는 추상 클래스보다 상대적으로 자유로운 관계 묶음이 가능하다. 서로 관련성이 없는 클래스들이더라도 같은 동작을 한다면 하나의 인터페이스로 묶을 수 있다. 심지어 다중 상속도 가능하기 때문에 다양한 인터페이스를 implements(구현)해서 기능을 구현할 수 있다.

 

인터페이스는 상속 관계를 타고 올라갔을 때 다른 조상 클래스를 상속하더라도, 같은 기능이 필요할 경우 사용한다. 클래스와 별도로 구현 객체가 같은 동작을 한다는 것을 보장하기 위해 사용한다.

 

이처럼 자유로운 관계 묶음이 필요할 때 인터페이스를 사용할 수 있다. 

 

특히, is able to 관계에 있을 때 인터페이스를 사용할 수 있다. 이러한 이유 때문인지 인터페이스명은 ~able로 끝나는 경우가 많다. 어떤 기능 또는 행위를 하는데 필요한 메서드를 제공한다는 의미를 강조하기 위해서다.

ex) Comparable, Serializable... 등

 

다중 상속이 필요한 경우

[그림 2]에서 사람은 말을 할 수 있다는 기능인 speak()를 추가한다고 하자.

이 또한 나중에 말을 할 수 있는 생물체가 나타날 수도 있으므로(물론 가능성이 낮지만) 추상화시켜 본다고 하자.

 

 

[그림 3] 인터페이스 다중 상속


인터페이스는 다중 상속(구현)이 가능하기 때문에 "사람 implements 말할 수 있는"이 가능하다.

 

정리

정리하자면,

추상 클래스는 is kind of 관계로 관련성이 높은 명확한 계층 구조의 추상화에 사용되며,

인터페이스는 is able to 관계로 관련성이 낮더라도 기능에 따른 추상화가 필요하거나 다중 상속(구현)이 필요할 때 사용된다.


참고자료

https://devlog-wjdrbs96.tistory.com/370

https://metafor.notion.site/54d624628a634c879cc93d94f54cd2d1#aa5c91ccff0b47688a8cdaa9aa210e85

https://code-lab1.tistory.com/287

https://myjamong.tistory.com/150

 

728x90
반응형