본문 바로가기

IT/JAVA

인터페이스(Interface)와 추상 클래스(abstract class) 비교 및 사용 시기에 대해서

추상화란 클래스 간의 공통된 부분을 일반화한 것을 의미합니다.

오늘 설명하고자 하는 인터페이스와 추상화 클래스는 둘 다 추상화 개념을 가지고 있습니다.

이는 추상화 메소드를 사용함으로서 보여집니다.

 

추상화 메소드

추상화 메소드란 자식 클래스에서 반드시 오버라이딩해야만 사용할 수 있는 메소드를 의미합니다.

그래서 상속받는 클래스들의 공통된 부분을 추상화 메소드로 나타내고, 상속받는 클래스가 강제적으로 추상 메소드를 구현하게 만듭니다.

강제성을 부여하는 이유?
- 클래스들의 공통된 필드와 메소드의 이름을 통일하기 위해서

- 클래스들의 공통사항을 한 곳에서 관리할 수 있어 개발 및 유지보수가 편리해지기 때문에

 

따라서 구현은 자식 클래스에서 이뤄지므로 추상 메소드는 다음과 같은 문법으로 선언만 됩니다.

abstract 리턴타입 메소드이름();

그리고 이러한 추상화 메소드를 하나 이상 가지고 있는 클래스를 추상화 클래스라고 합니다.

 

인터페이스와 추상화 클래스

다만 추상화 클래스를 좀 더 세부적으로 구분하게 되는데,

일반적으로 추상화 클래스는 추상화 메소드 뿐만 아니라 일반 메소드, 생성자, 필드 등 일반 클래스와 다름 없이 구현이 가능한 클래스를 말하고, 추상화 메소드(public abstract class)와 상수(public static final)로만 구현이 가능한 것은 인터페이스라고 따로 정의하고 있습니다.

 

추상화 클래스는 다음과 같이 작성하고

abstract class 클래스이름 {
    //필드
    int a;
    
    //생성자
    public 클래스이름(int a) {
    	this.a = a;
    }
    
    //메소드
    void hello() {
    	System.out.println("hello");
    }
    
    //추상메소드
    abstract void bye();
    ...
}

 

인터페이스는 다음과 같이 작성합니다.

interface 인터페이스명 {
	//상수
	public static final 변수;
    
    	//추상 메소드
   	public abstract 메소드();
}

 

그럼 이 두 가지를 구분해서 사용하는 이유가 무엇일까요?

 

가장 큰 차이점은 다중 상속의 가능 여부입니다.

다중 상속은 해당 클래스가 다양한 동작을 수행할 수 있도록 하면서도, 상속하지 않은 것보다 코드가 간결하고, 개발속도를 높일 수 있다는 장점이 있습니다.

 

그러나 자바는 다중 상속을 지원하지 않습니다. 다중 상속으로 인해 발생하는 모호함을 자바는 좋아하지 않기 때문입니다.

여기서 말하는 모호함이란, 

class A {
	void hello() {
		System.out.println("A의 hello");
	}
}

class B {
	void hello() {
		System.out.println("B의 hello");
	}
}

위에 코드와 같이 클래스 A, B가 같은 이름의 메소드를 가지고 있는 상태에서

클래스 C가 A, B를 둘 다 상속하는 경우 hello 메소드를 호출하고자 할 때 어느 것을 호출해야 하는지 알지 못하는 문제가 있고 이것을 모호하다고 합니다.

 

하지만 딱 한 가지, 인터페이스를 통해서는 다중 상속을 가능하게 합니다.

어떻게 다중 상속이 가능할까요?

 

바로 인터페이스에 선언 가능한 것을 추상화 메소드와 static final 변수로 국한함으로써 충돌이 발생할 가능성을 없앴기 때문입니다.

추상화 메소드는 상속 받으면 반드시 오버라이딩해야만 하고, 

static final 변수는 다른 곳에서 같은 이름의 변수를 선언할 수도, 변수 값을 변경할 수도 없습니다.

생략된 제어자는 컴파일시 javac(자바 컴파일러)가 자동으로 추가해줍니다.
따라서 생략해도 자동으로 변수는 public static final로, 메소드는 public abstract으로 설정되지만 코드의 가독성을 위해 명시적으로 작성하는 것이 좋습니다.

 

반대로 추상 클래스는 추상 메소드를 제외하면 일반 클래스와 다름이 없기 때문에 다중 상속이 불가능합니다.

 

상속 방법

인터페이스는 클래스에 implements로, 추상 클래스는 extends로 상속됩니다.

//인터페이스
class useInterface implements interfaceExample {
}

//추상 클래스
class useAbstractClass extends abstractClassExample {
}

 

그렇다면 implements와 extends의 차이는 무엇일까요?

  • implements는 단어 뜻 그대로 구현하기 위해 사용합니다. 즉, 인터페이스는 선언부만 존재하기 때문에 상속받는 클래스가 이를 구현해야 하므로 implements를 사용합니다.

 

  • extends는 부모에서 선언하고, 구현한 것을 자식이 그대로 사용할 수 있을때 사용합니다. 따라서 일반적인 클래스는 상속받을 때 extends를 사용합니다. 추상 클래스의 경우, 일반 클래스와 interface의 기능이 혼합되어 있지만 extends를 사용합니다.

 

그림으로 정리해보자면 다음과 같습니다.

상속 방법

interface - interface 상속시 extends를 사용하는 이유는

구현이 아니라 물려주는 의미로 사용되기 때문에 클래스끼리 상속할 때처럼 extends를 사용합니다.

 


이번 시간은 인터페이스와 추상 클래스에 대해 알아봤습니다.

 

인터페이스와 추상 클래스는 언제 어떤 용도로 구분해서 사용하면 좋을까요?

이 부분은 사람마다 다르기 때문에 저의 생각을 말해보겠습니다.

추상클래스 사용 시기

abstract class creature {
	int age;
	
	public creature(int age) {
		this.age = age;
	}
	
	void grow() {
		this.age++;
	}
	
	abstract void move(String method);
}

위 예제를 보면 생명체는 공통적으로 나이를 먹고, 나이를 먹는 방법도 같기 때문에(시간이 지나면 먹는다.) grow() 메소드를 자손 클래스마다 오버라이딩을 할 필요가 없습니다

반면 move() 메소드는 하나의 기능이지만 생명체마다 행위가 다르기 때문에 추상 메소드로 선언했습니다.

 

=> 이처럼 추상화 클래스하나의 기능이 행동까지 일치하는 공통된 부분을 가지고 있으면서, 하나의 기능이 상황에 따라 행동이 다른 부분(추상 메소드)도 있을 때 사용하면 좋다고 생각합니다.

 

인터페이스 사용 시기

인터페이스는 하나의 기능이 상황에 따라 행동이 다른 부분만(추상 메소드) 있을 때 사용하면 좋다고 생각합니다.

 

 

추상 클래스와 인터페이스 사용 시기

 

한 줄로 설명하자면,

추상메소드만 선언할꺼면 인터페이스를, 다른 일반 메소드나 필드도 함께 필요하면 추상클래스를 쓰면 된다고 생각합니다.

 

잘못된 정보가 있거나, 다른 의견이 있으시다면 피드백 부탁드립니다. 감사합니다 :)