본문 바로가기
기본기 다지기/문법

추상클래스(abstract class)와 인터페이스(interface)

by -MAYDAY- 2022. 3. 27.

추상클래스(abstract class)와 인터페이스(interface)

 객체지향 프로그래밍을 할 때 항상 추상클래스와 인터페이스 중에 어느 것을 선택해서 구현해야 되는 지에 대해 고민이 많았다. 어떠한 상황에서 추상클래스를 써야할 지, 또는 추상클래스 대신에 인터페이스를 쓰는 것이 더 나을 지에 대한 확신이 서지 않았다. 그럴 때마다 딜레마에 빠진 내가 검색을 통해서 차이를 비교한다음 추상클래스와 인터페이스를 선택해서 구현하는 상황이 너무 잦다보니 이 참에 두 형태의 의미와 차이를 비교해서 확실히 알아두려고 한다.

 

 

추상클래스(abstract class)란?

 표면적인 의미 그 자체로 추상적으로 설계된 클래스이다. 일반적인 클래스가 가진 구성에 비해 많이 축소되고 간략한 형태를 가진 클래스라고 볼 수 있다. 대개 일반적인 상황에서 변수와 함수를 선언하는 정도에서만 그칠 수 있고 실제 기능은 구현되어 있지 않은 클래스이다. 왜 이러한 클래스가 필요할까? 추상클래스의 필요성에 대해서는 아래와 같은 이유가 있다.

 

1. 추상화

2. 코드의 표준화

3. 같은 이름 다른 기능 정의

 

 - 추상화

 추상화는 불필요한 기능을 없애고 공통적인 특성이나 기능을 간결하게 하나로 묶어 이름을 정의하는 것이라고 볼 수 있다. 추상화를 통해 불필요한 부분을 생략하고 객체의 속성 중 가장 중요한 것에만 중점을 두어 개략화하여 클래스를 설계하는 것이다. 추상클래스를 통해서 추상화를 실천할 수 있게 된다.

 

 - 코드의 표준화

 추상클래스를 통해 표준화를 실천하여 코드의 복잡성을 덜어내고 일관된 코드를 설계할 수 있다. 협업을 하는 데 있어 가장 중요한 것이 규칙을 준수하여 원하는 공동 목표를 이루어내는 것이다. 코드를 표준화하여 설계함으로써 협업하는데 혼란을 최소화할 수 있다. 추상클래스도 코드 표준화를 이루어내는 하나의 방법으로써 효율적인 협업을 실현하는 좋은 수단이 될 수 있다.

 

 - 같은 함수 다른 기능 정의

 같은 함수이지만 상속 받는 자식 클래스에 따라 다소 다른 기능을 구현하여 자식 클래스마다 수행하고자 하는 기능을 정의할 수 있다. 그렇기에 똑같은 부모 클래스를 상속받는 자식 클래스마다 이름은 같지만 다른 기능을 수행하는 함수를 가지고 있다. 

 

 본격적으로 추상클래스가 무엇인지 예시를 통해서 더 쉽게 알아보자.

 

public abstract class Parent {
    public int abs_a;
    public int abs_b = 200;
    final int abs_c = 300;

    abstract void abs_method();

    void method() {
        System.out.println("This is normal Method");
    }
    
    static void s_method() {
        System.out.println("This is static Method");
    }
}

 추상클래스는 멤버 변수를 정의할 수 있고, 추상메소드 선언과 일반메소드의 정의 모두 가능하다. 일반적인 클래스와는 다르게 추상화라는 주된 목적을 실현하기 위해 등장한 클래스이다. 추상메소드 선언이 가능하다는 특성을 통해 이를 상속받는 어느 자식 클래스에서도 추상메소드를 Overriding하여 각 자식클래스가 원하는 기능을 구현할 수 있게 된다.

 

 추상클래스를 상속받는 자식 클래스를 살펴보며 추상클래스의 특성을 자세하게 살펴보자.

 

public class Child2 extends Parent{
    @Override
    void abs_method() {
        System.out.println("This abs_method called from Child2");
        abs_a = 100;
        System.out.println("abs_a : " + abs_a);
        System.out.println("abs_c : " + abs_c);
        s_method();
    }

    @Override
    void method() {
        super.method();
        System.out.println("This method called from Child2");
        System.out.println("abs_b : " + abs_b);
        System.out.println("abs_c : " + abs_c);
        s_method();
    }
}

추상클래스를 상속받는 자식 클래스의 코드를 살펴보면 추상메소드의 기능을 정의하거나 일반메소드의 기능을 추가 혹은 수정을 위한 재정의가 가능한 것을 볼 수 있다. 또한 추상클래스의 멤버 변수에 접근 가능하여 이를 호출하거나 값을 변경하는 등 자식 클래스가 원하는대로 추상클래스의 멤버 변수를 활용할 수 있다. 마찬가지로 추상클래스에서 정의한 상수도 자식 클래스에서 접근 가능하여 호출을 통해 활용이 가능하다.

 

 

인터페이스(interface)란?

 인터페이스는 사전적 의미로는 객체와 객체 사이의 상호작용을 위한 매개체, 또는 하나의 시스템에서 2개의 구성 요소가 상호작용할 수 있도록 접속되는 경계로 정의할 수 있다. 하지만 SW에서 정의한 인터페이스는 동일한 목적 하에 동일한 기능을 정의하도록 클래스에 강제하는 것이라고 표현할 수 있다. 긍정적인 표현으로는 클래스가 구현해야 할 요소를 정해주어 인터페이스에서 선언한 추상적 기능을 잊지 않고 모두 구현할 수 있게 해주는 역할도 수행해준다고 볼 수 있다. 추상클래스의 사용 목적과 동일하게 추상화 및 코드의 표준화 등을 통해 원활한 협업과 소스 코드의 일관성을 유지하는 데 효과적으로 사용할 수 있다.

 

 원래 인터페이스는 추상클래스가 가지고 있는 특성보다 제한적인 특성을 가지고 있다. 추상클래스와 다르게 상수 정의 및 추상메소드 선언 정도만 가능하다는 특징을 가지고 있다. 하지만 Java 8에서 몇 가지 기능이 개선됨에 따라 인터페이스의 제한사항이 일부 개선되었다.

 

 일단 예제 코드를 통해 인터페이스의 본래 주요 특성을 살펴보자.

 

public interface IParent {
    int num1 = 1000;
    int num2 = 2000;

    void absMethod();
}

 인터페이스는 추상클래스와 다르게 추상메소드에 'abstarct' 키워드를 붙이지 않는다. 붙이지 않아도 인터페이스 안에서 선언하는 함수는 추상메소드로 인식한다. 이러한 점을 볼 때 추상클래스에 비해서 덜 신경써도 되는 부분이라고 할 수 있을 것 같다.

 그리고 인터페이스에서는 추상클래스와 달리 멤버 변수 선언과 접근제어자 설정이 불가하다. 상수 정의만 가능하다는 점에서 추상클래스가 제공하는 기능에 비해서는 다소 제한적인 느낌을 받을 수 있다. 그래도 다행스럽게도(?) static 메소드 정도는 정의할 수 있어 이를 참조하는  클래스에서는 static 메소드에 접근 가능하다.

 

 추상클래스에 비해서 다소 제한사항이 있음에도 불구하고 인터페이스만의 매력적인 장점을 가지고 있는데 아래의 예제 코드를  통해 확인해보자.

 

public interface IParent2 {
    int num3 = 3000;
    int num4 = 4000;

    void anotherMethod();
}

 위와 같은 또 다른 인터페이스를 선언하였다. 2개의 인터페이스를 정의했는데 과연 클래스에서는 이 2개의 인터페이스 모두 참조할 수 있을까?

 

public class Child implements IParent, IParent2 {
    @Override
    public void absMethod() {
        System.out.println("This method is absMethod");
        System.out.println("num1 = " + num1);
        System.out.println("num3 = " + num3);
    }

    @Override
    public void anotherMethod() {
        System.out.println("This method is anotherMethod");
        System.out.println("num2 = " + num2);
        System.out.println("num4 = " + num4);
    }
}

 놀랍게도 클래스가 2개의 인터페이스 모두 참조 가능하여 두 인터페이스의 모든 추상메소드의 기능을 정의할 수 있다. 게다가 특정 인터페이스에서 정의한 static 메소드라도 이를 참조하여 구현하는 클래스에서는 어디에서든지 호출이 가능하다. 상수도 마찬가지로 클래스의 어디에서든 호출이 가능하다.

추상클래스는 자식클래스에서 단 하나의 추상클래스를 상속받을 수 밖에 없는 특성을 가지고 있었지만 인터페이스는 이러한 추상클래스의 한계를 극복한 형태라고 볼 수 있다. 그렇기에 클래스에서 여러 인터페이스의 특성을 가지고 있다면 주저없이 공통된 특성을 가지고 있는 모든 인터페이스를 참조할 수 있다.

 

 여기까지가 일반적인 인터페이스의 특성이라고 볼 수 있다. 앞서 잠깐 언급하였듯이 Java 8에서 'default' 키워드를 통한 일반 메소드 정의가 가능해졌고, static 메소드도 정의가 가능해지면서 인터페이스를 보다 유용하게 사용 가능해졌다. 예시를 통해 살펴보자.

 

public interface IParent3 {
    default void normalIMethod() {
        System.out.println("default method in Interface!");
    }
    
    static void ISMethod() {
        System.out.println("This is static Method in Interface");
    }
}

 이처럼 Java 8에서 'default'와 'static'을 통해 인터페이스에서 일반 메소드와 static 메소드를 정의할 수 있어 이전의 인터페이스 한계를 일부 극복했다. (이와 같은 여러 큰 변화가 Java 8에서 이루어지다보니 이래서 대부분의 국내 기업 혹은 기관에서 최소 Java 8이상부터 사용하는 것 같다.)

 

public class Child implements IParent, IParent2, IParent3 {

    @Override
    public void absMethod() {
        System.out.println("This method is absMethod");
        System.out.println("num1 = " + num1);
        System.out.println("num3 = " + num3);
        IParent3.ISMethod();
        normalIMethod();
    }

    @Override
    public void anotherMethod() {
        System.out.println("This method is anotherMethod");
        System.out.println("num2 = " + num2);
        System.out.println("num4 = " + num4);
        IParent3.ISMethod();
        normalIMethod();
    }

    @Override
    public void normalIMethod() {
        IParent3.super.normalIMethod();
        System.out.println("This method called from Child");
    }
}

 이처럼 자식 클래스에서 인터페이스를 상속받게 되면, 인터페이스에서 정의한 일반 메소드를 다른 메소드에서 호출하거나, 클래스 내에서 Overriding하여 기능을 수정하거나 더 추가하여 구현할 수 있다. 또한 인터페이스에서 정의한 static 메소드를 호출 가능하여 자식 클래스 내 원하는 곳에서 적절히 활용 가능하다.

 

 

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

  추상클래스 인터페이스
접근제어자 가능 'public', 'default'만 가능
상수 정의 가능 가능
멤버 변수 가능 불가능
추상 메소드 가능 가능('abstract' 키워드 생략 가능)
일반 메소드 가능 가능('default' 키워드 필요)
다중 상속 불가능 가능

 

 추상클래스와 인터페이스의 차이를 알아보기 쉽게 표로 정리하였다. 개발할 때 추상클래스와 인터페이스의 모호함으로 인해 이를 적절하게 활용하여 코드를 작성하는 것이 어려웠다. 하지만 이번 계기를 통해서 추상클래스와 인터페이스에 대해 자세히 알아보게 되었고, 이러한 차이로 인해 필요한 상황에서 적절하게 선택하여 구현할 수 있겠다는 확신이 들게 되었다. 앞으로는 무조건 추상클래스만 활용하거나 인터페이스만 활용하는 것이 아닌 적당한 상황에서 추상클래스와 인터페이스를 적절하게 활용하는 것을 실천하여 소스 코드의 일관성과 간결함을 유지하는데 노력해야겠다.

 

 

- 참고

https://aileen93.tistory.com/107

 

[JAVA] 추상(abstract) 클래스와 인터페이스(interface) 클래스

추상(abstract) 클래스와 인터페이스(interface) 클래스 1. 추상  클래스와  추상 메소드란? 추상클래스란 말 그대로 추상적으로 밖에 그려지지 않은 클래스라고 한다. 즉, 클래스가 전체적인 구성을

aileen93.tistory.com

https://sungwoon.tistory.com/58

 

[Java-자바]추상 클래스 및 추상 메서드(abstract class and abstract method)

추상메서드란 "추상" 사전적 의미로 "여러가지 사물이나 개념에 공통되는 특성이나 속성따위를 추출하여 파악하는 작용"이라는 의미이다. 추상화 : 클래스간의 공통점을 찾아내서 공통의 부모

sungwoon.tistory.com

https://velog.io/@codemcd/인터페이스Interface

 

[JAVA] 인터페이스(Interface)

인터페이스란? 인터페이스의 사전적 의미는 다음과 같다. > 하나의 시스템을 구성하는 2개의 구성 요소(하드웨어, 소프트웨어) 또는 2개의 시스템이 상호작용할 수 있도록 접속되는 경계(boundary),

velog.io

https://codingwell.tistory.com/73

 

[Java] 인터페이스(interface)

인터페이스란? 인터페이스는 객체와 객체 사이에서 상호작용의 매개로 쓰이는데, 일종의 추상클래스이다. 그러나 추상클래스보다 추상화 정도가 높아서 일반 메소드나 멤버변수를 구성원으로

codingwell.tistory.com

https://math-coding.tistory.com/169

 

[Java] 자바 인터페이스(Interface) 사용

이 글은 "자바 온라인 스터디" 내용을 공부하여 작성하였습니다. 인터페이스(Interface)란? 현업에서 소스코드 작성 시 클래스를 처음부터 구현하게 된다면 코드의 가독성도 떨어지고 시간도 오래

math-coding.tistory.com

 

'기본기 다지기 > 문법' 카테고리의 다른 글

enum & struct  (0) 2022.06.12
Thread 실습 - Java  (0) 2022.04.16
Thread란?  (0) 2022.04.03
static 이란?  (0) 2022.03.13