[Java] 추상 클래스 & 인터페이스 차이 정리
서론
이번 글에서는 자바의 개념 중에서 많이 헷갈리는 추상 클래스와 인터페이스의 차이에 대해서 살펴보고자 한다.
추상 클래스와 인터페이스 개념 및 예제를 통해 어떤 차이가 있는지 알아보자.
기술 면접에서 두 차이에 대한 질문이 빈번하게 나오기 때문에 정확한 개념와 특징을 잘 알아두어야 한다.
추상 클래스 (Abstract Class)
자바에서 추상 클래스는 1개 이상의 추상 메서드(Abstract Method)를 포함하는 클래스를 의미한다.
- 추상 메서드 : 자식 클래스에서 반드시 오버라이딩(Overriding) 하여 사용해야 하는 메서드로 선언부만 존재하고 구현부가 없는 메서드를 의미한다.
// 아래 코드와 같이 선언부만 존재하며 메서드 안에는 구현되어 있지 않다.
abstract void func();
즉, 추상 클래스는 일반 클래스와 거의 다를 것이 없지만 추상 메서드가 포함되었다는 차이가 존재한다.
추상 클래스를 사용하는 이유는 객체 지향 프로그래밍(OOP)의 주 특징 중 하나인 다형성(Polymorphism)을 보장할 수 있기 때문이다.
따라서 추상 클래스에서 정의된 추상 메서드는 해당 클래스를 상속 받는 모든 자식 클래스에서 반드시 재정의하여 사용하여야 한다.
예제를 통해 추상 클래스를 이해해보자.
public abstract class Animal {
abstract void cry();
void working(String animal) {
System.out.println(animal + "가 걸어갑니다.");
}
}
class Dog extends Animal {
@Override
void cry() {
System.out.println("멍멍");
}
}
class Cat extends Animal {
@Override
void cry() {
System.out.println("야옹");
}
}
위 예제는 Animal 이라는 부모 클래스와 Dog, Cat 이라는 2개의 자식 클래스로 이루어져 있다.
추상 클래스인 Animal 클래스는 추상 메서드 cry() 메서드를 포함하고 있는 것을 확인할 수 있으며, 일반 클래스와 성질이 동일하기 때문에 일반 메서드인 working() 메서드 또한 포함되어 있는 것을 확인할 수 있다.
cry() 메서드를 추상 메서드로 선언한 이유는 강아지와 고양이는 울음 소리가 다르기 때문에 cry() 메서드를 실행했을 때 각각 다른 로직이 수행되도록 하여야 한다. 따라서 Dog, Cat 클래스는 부모 클래스의 cry() 메서드를 오버라이딩 해야 인스턴스를 생성할 수 있다.
메인 클래스에서 코드 수행 결과를 살펴보자.
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
dog.cry();
dog.working("강아지");
Cat cat = new Cat();
cat.cry();
cat.working("고양이");
}
}
// 멍멍
// 강아지가 걸어갑니다.
// 야옹
// 고양이가 걸어갑니다.
자식 클래스는 부모에서 상속받은 동일한 메서드 cry()를 호출하지만 출력 결과는 각각 다른 것을 확인할 수 있다.
또한, working() 메서드는 일반 메서드이므로 부모 클래스의 메서드를 공통적으로 사용할 수도 있다.
추상 클래스의 특징을 정리해보면 다음과 같다.
- 추상 클래스를 상속 받는 자식 클래스는 반드시 추상 메서드를 오버라이딩(Overriding) 하여 사용해야 한다.
- 추상 메서드가 1개 이상 포함된 클래스는 반드시 추상 클래스로 정의하여야 한다.
- 자식 클래스는 추상 클래스를 다중 상속할 수 없다.
- extends 키워드를 사용하여 상속을 받는다.
인터페이스 (Interface)
인터페이스는 추상 클래스와 달리 모든 메서드가 구현되어 있지 않고 선언부만 존재하는 것을 의미한다.
따라서 일반 메서드, 생성자 등을 포함할 수 없으며 오직 추상 메서드와 상수만을 포함한다는 특징을 가지고 있다.
- 상수는 public static final 로 시작하여야 하며 생략 가능하다.
- 추상 클래스는 public abstract 로 시작하여야 하며 생략 가능하다.
또한 추상 클래스와 동일하게 인터페이스를 상속 받는 자식 클래스는 인터페이스에 정의된 모든 추상 메서드를 오버라이딩 하여 사용하여야 한다는 특징이 있다.
예제를 통해 인터페이스를 이해해보자.
public interface Animal {
public static final String type = "나는 동물입니다";
public abstract void cry();
}
class Dog implements Animal {
@Override
public void cry() {
System.out.println("멍멍");
}
}
class Cat implements Animal {
@Override
public void cry() {
System.out.println("냐옹");
}
}
추상 클래스와 거의 비슷하지만 extends 대신에 implements를 사용한 것을 확인할 수 있다.
이제 메인에서 코드를 실행하여 결과를 확인해보자.
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
Cat cat = new Cat();
dog.cry();
System.out.println(dog.type);
cat.cry();
System.out.println(cat.type);
}
}
// 멍멍
// 나는 동물입니다
// 냐옹
// 나는 동물입니다
인터페이스의 특징을 정리해보면 다음과 같다.
- 추상 클래스와 달리 자식 클래스는 다중 상속이 가능하다.
- implements 키워드를 사용하여 상속을 받는다.
정리하기
추상 클래스의 상속 관계는 부모의 특성을 자식 클래스에서 기능 추가 및 기능을 확장하여 사용하는 의미를 지니는 반면에, 인터페이스는 기능 확장의 개념보다는 자식 클래스에서 어떤 메서드를 사용해야 하는지를 정의해주는 의미를 가지고 있다.
- extends : 상위 클래스의 특성을 모두 물려받아 기능 추가 및 확장을 할 때 사용
- implements : 상위 인터페이스에서 정의된 추상 메서드를 강제로 구현해야할 필요가 있는 경우에 사용