자바에서 추상 클래스와 인터페이스는 추상화를 구현하는 데 핵심적인 역할을 합니다. 객체 지향 프로그래밍에서 추상화란, 복잡한 구현 세부 사항을 사용자로부터 숨기는 과정을 의미합니다.
추상화를 통해 우리는 기능이 무엇인지는 알 수 있지만, 그것이 어떻게 구현되는지는 알 수 없습니다.
이제 각각을 자세히 살펴보고, 왜 이러한 개념이 사용되는지 이해해 보도록 하겠습니다.
추상 클래스
자바에서 추상 클래스는 객체로 직접 인스턴스화할 수 없는 클래스를 의미하며, 추상 메서드를 포함하거나 포함하지 않을 수 있습니다. 추상 메서드란, 선언 시 구현 코드가 없는 메서드를 말합니다.
다음은 오라클에서 제공하는 ‘GraphicObject’ 추상 클래스의 예시입니다.
추상 클래스는 ‘abstract’ 키워드를 ‘class’ 키워드 앞에 붙여서 정의할 수 있습니다.
abstract class AbstractClass { void run() { System.out.println("실행됨"); } }
추상 클래스는 다른 클래스에 의해 상속될 수 있으며, 이는 하위 클래스를 통해 확장될 수 있음을 의미합니다.
abstract class AbstractClass { void run() { System.out.println("실행됨"); } } class ExtendedAbstractClass extends AbstractClass { void newMethod() { System.out.println("새로운 메서드"); } @Override void run() { System.out.println("재정의됨"); } }
추상 클래스는 동일한 추상 클래스를 확장하는 여러 클래스 간에 공통적인 메서드 구현을 공유하는 데 유용합니다. 또한, 추상 클래스 내에서 추상 메서드를 정의함으로써, 메서드는 비슷하지만 구현이 다른 여러 클래스에서 유연하게 사용될 수 있습니다. 다음 예시를 통해 좀 더 자세히 살펴보겠습니다.
출발, 정지, 후진과 같은 기본적인 기능을 가진 자동차를 생각해 봅시다. 이러한 기능들은 모든 종류의 자동차에 공통적으로 적용됩니다.
그렇다면 자율 주행과 같은 자동화 기능은 어떨까요? 이러한 기능의 구현은 자동차의 종류에 따라 다를 수 있습니다. 이러한 상황을 반영하여 객체 지향 프로그램을 어떻게 설계할 수 있는지 알아보겠습니다.
먼저, 여러 종류의 자동차 클래스가 상속할 수 있는 기반 클래스인 ‘Car’ 추상 클래스를 생성합니다.
abstract class Car { void start() { // 구현 코드 System.out.println("자동차가 시동됩니다."); } void stop() { // 구현 코드 System.out.println("엔진이 정지합니다."); } void reverse() { // 구현 코드 System.out.println("후진 모드가 활성화됩니다."); } abstract void selfDrive(); }
‘start()’, ‘stop()’, ‘reverse()’ 메서드는 모든 자동차에 공통적으로 사용되므로 ‘Car’ 클래스 내에서 구현이 정의됩니다. 하지만, 자율 주행 모드는 자동차 유형에 따라 구현이 다를 수 있으므로 ‘selfDrive()’ 메서드는 추상 메서드로 정의하고, 각 자동차 클래스에서 고유하게 구현할 수 있도록 합니다.
class CarTypeA extends Car { @Override void start() { super.start(); } @Override void stop() { super.stop(); } @Override void reverse() { super.reverse(); } @Override void selfDrive() { // 사용자 정의 구현 System.out.println("A 타입 자율 주행 모드 활성화"); } }
class CarTypeB extends Car { // ... 유사한 메서드들 @Override void selfDrive() { // 사용자 정의 구현 // B 타입은 다른 구현을 가짐 System.out.println("B 타입 자율 주행 모드 활성화"); } }
주의해야 할 점은, 만약 하위 클래스가 추상 클래스에 정의된 모든 추상 메서드를 구현하지 않는다면, 해당 하위 클래스 역시 추상 클래스로 선언해야 한다는 것입니다.
인터페이스
인터페이스는 클래스가 반드시 구현해야 하는 메서드를 명시하는 방법입니다. 자동차의 예를 다시 들자면, 자동차는 기본적인 기능들을 가지고 있습니다. 시동을 걸고, 움직이고, 정지하는 기능입니다. 이러한 기능들은 모든 자동차에 공통적입니다.
따라서, 클래스가 자동차 인터페이스를 구현한다면, 그 자동차가 적절하고 안전하게 작동하기 위해서는 모든 메서드가 구현되어야 합니다.
추상 클래스와 마찬가지로 인터페이스의 객체를 인스턴스화하거나 생성할 수 없습니다. 인터페이스는 추상 메서드, 즉 구현 코드가 없는 메서드만을 포함하기 때문에 완벽한 추상 클래스로 간주할 수 있습니다.
‘interface’ 키워드를 사용하여 인터페이스를 만들 수 있습니다.
interface Car { void start(); void stop(); void move(); }
클래스를 정의할 때, ‘implements’ 키워드를 사용하여 인터페이스를 구현합니다.
class CarTypeB implements Car { public void start() { System.out.println("시작됨"); } public void stop() { System.out.println("정지됨"); } public void move() { System.out.println("움직임"); } }
유사점
추상 클래스와 인터페이스의 공통점은 객체로 인스턴스화할 수 없다는 것입니다.
차이점
추상 클래스 | 인터페이스 | |
상속 및 구현 | 클래스는 하나의 추상 클래스만 상속할 수 있습니다. | 클래스는 여러 인터페이스를 구현할 수 있습니다. |
변수 유형 | 최종 변수, 비최종 변수, 정적 변수, 비정적 변수를 가질 수 있습니다. | 정적 변수와 최종 변수만 가질 수 있습니다. |
메서드 유형 | 추상 메서드와 비추상 메서드를 모두 포함할 수 있습니다. | 추상 메서드만 포함할 수 있지만, 정적 메서드는 예외입니다. |
접근 제어자 | 추상 클래스에는 접근 제어자가 있을 수 있습니다. | 인터페이스에 정의된 메서드 시그니처는 기본적으로 공개됩니다. 인터페이스에는 접근 제어자가 없습니다. |
생성자 및 소멸자 | 생성자와 소멸자를 선언할 수 있습니다. | 생성자 또는 소멸자를 선언할 수 없습니다. |
속도 | 느림 | 빠름 |
추상 클래스와 인터페이스의 차이점
추상 클래스와 인터페이스는 언제 사용해야 할까요?
다음과 같은 경우 추상 클래스를 사용합니다.
- 여러 클래스 간에 일부 공통 메서드와 필드를 공유하려는 경우.
- 묶여있는 객체의 상태를 수정하기 위해 비정적 및 비최종 필드를 선언하는 경우.
다음과 같은 경우 인터페이스를 사용할 수 있습니다.
- 인터페이스를 구현하는 클래스의 동작을 정의하고 싶지만, 구현 방식은 중요하지 않은 경우.
- 클래스가 제대로 작동하기 위해 모든 메서드를 구현했는지 확인하고 싶은 경우.
마지막 말
인터페이스는 실제 구현에 대한 걱정 없이 기능을 구현할 수 있는 구조를 제공하기 때문에 주로 API를 개발하는 데 사용됩니다.
추상 클래스는 일반적으로 코드를 더 재사용할 수 있도록 추상 클래스를 상속하는 여러 클래스 간에 공통 추상 및 비추상 메서드를 공유하는 데 사용됩니다.
자바 온라인 과정을 통해 Java에 대해 더 자세히 알아보세요. 자바 인터뷰를 준비 중이신가요? 다음은 객체 지향 프로그래밍에 대한 몇 가지 인터뷰 질문입니다.