Search

추상 클래스와 인터페이스

태그
Java
면접질문
작성 상태
작성 완료
작성일
2023/05/28
참고 링크
참고 링크 2
추상 클래스와 인터페이스는 모두 다형성을 구현할 때 사용하기 적절하다. 그런데 두개가 따로 있는데는 분명히 이유가 있을 터, 두가지의 특징을 알아보고 언제 어떤 것을 사용할지 정리해 보았다.

공통점

다형성을 위한 것이다.

구현부 없이 오직 선언부만 있는 메서드인 추상 메서드를 통해 다형성을 구현한다.

추상클래스로 다형성

public abstract class Animal{ protected abstract void move(); } class Dog extends Animal{ protected void move(){ System.out.println("네발로 걷는다."); } } class Bird extends Animal{ protected void move(){ System.out.println("하늘을 난다."); } } class Main{ public static void main(String[] args){ Animal dol = new Dog(); //dol = new Bird(); dol.move(); } }
Java
복사

인터페이스로 다형성

public interface Moveable{ void move(); } class Dog implements Moveable{ public void move(){ System.out.println("네발로 걷는다."); } } class Bird implements Moveable{ public void move(){ System.out.println("하늘을 난다."); } } class Main{ public static void main(String[] args){ Moveable dol = new Dog(); //dol = new Bird(); dol.move(); } }
Java
복사

차이점

추상 클래스의 특징

일반 필드와 메서드를 가질 수 있다.

public abstract class Animal{ public final void eat(){ howToEat(); } protected abstract void howToEat(); protected abstract void move(); }
Java
복사
추상 클래스에 본문이 있는 메서드를 자유롭게 추가할 수 있다. 또한 이들의 접근제어자와 final, static 등의 키워드도 자유롭게 붙일 수 있다.
public abstract class Animal{ protected String name; protected abstract void howToEat(); protected abstract void move(); }
Java
복사
이런 식으로 필드도 추가할 수 있다.
public abstract class Animal{ protected String name; public Animal(String name){ this.name = name; } protected abstract void howToEat(); protected abstract void move(); }
Java
복사
생성자도 추가할 수 있다. 단, new Animal() 처럼 추상클래스의 생성자를 직접 호출할 수는 없고, 하위 클래스의 생성자에서 super() 로만 호출할 수 있다.

메서드의 접근 제어자를 자유롭게 설정할 수 있다.

일반메서드, 추상메서드 모두 접근제어자를 자유롭게 선택할 수 있다. 단, 추상 메서드의 경우 하위클래스에서 반드시 오버라이드 해야 하므로 private 이 될 수 없다.
앞선 항목에서 예시로 보인 코드에 모두 포함되어있다.

클래스는 다중 상속이 불가능하다.

여러개의 클래스를 상속하는 것은 불가능하다. 추상클래스도 마찬가지.

인터페이스의 특징

기본적으로 public abstract 메서드로 선언된다.

메서드에 다른 접근 제어자를 붙일 수 없고, 굳이 public abstract 키워드를 사용할 필요도 없다.
아래 두 코드는 완벽하게 동일하다.
public interface Moveable{ public abstract void move(); }
Java
복사
public interface Moveable{ void move(); }
Java
복사
Java 8 부터 인터페이스에 default 메서드 기능과 static 메서드 기능이 추가되었다.
public interface Moveable{ default void moveBack(){ throw new UnsupportedOperationException("지원하지 않는 메서드 입니다."); } void move(); }
Java
복사
default 메서드는 인터페이스에 직접 구현하고, public 접근제어자를 가지며 구현 클래스에서 오버라이드 할 수 있다. 즉, default 메서드는 인터페이스의 일부 구현체만 해당 책임을 부여하거나, 일부 클래스만 책임을 수행하는 방법이 달라지는 경우에 적합하다. 혹은, 이미 인터페이스의 구현체가 여려개 있는 경우에 인터페이스를 수정해야 하는 경우에도 사용할 수 있다.

정적상수가 아닌 필드를 사용할 수 없다.

정적 필드가 아닌 필드를 선언할 수 없다. 인터페이스에서 필드를 생성하면 무조건 public static final 로 선언된다.
public interface Moveable{ public static final int MAX_SPEED = 299792458; public abstract void move(); }
Java
복사
public interface Moveable{ int MAX_SPEED = 299792458; void move(); }
Java
복사

다중 구현이 가능하다.

한 클래스가 여러개의 인터페이스를 구현할 수 있다.
public interface Moveable{ void move(); } class Dog implements Moveable, Comparable<Dog>{ private int age; public Dog(int age){ this.age = age; } public void move(){ System.out.println("네발로 걷는다."); } public int compareTo(Dog other){ return Integer.compare(this.age, otherPerson.age); } }
Java
복사

언제 무엇을 써야 하나

인터페이스에 default 메서드가 추가되고 나서 추상 클래스와 인터페이스 사이의 차이가 모호해졌다.

추상 클래스를 써야 하는 경우

공통된 필드가 필요한 경우 = 하위 클래스들이 공통된 속성을 사용하기 원하는 경우

인터페이스에 디폴트 메서드가 추가된 Java 8 이후에 인터페이스와 추상 클래스의 가장 큰 차이는 필드를 쓸 수 있냐 없냐의 차이다.
이게 가장 큰 이유다.

의미상으로 확장의 개념이 포함된 경우

즉, 하위 클래스 is kind of 추상클래스 의 관계가 성립하는 경우
public abstract class Animal{ protected abstract void move(); } class Dog extends Animal{ protected void move(){ System.out.println("네발로 걷는다."); } } class Bird extends Animal{ protected void move(){ System.out.println("하늘을 난다."); } }
Java
복사
위 코드처럼, Dog is kind of Animal 이 성립하므로 추상 클래스가 적합하다.

인터페이스를 써야 하는 경우

의미상으로 ~할 수 있다 혹은 has a 관계인 경우

하위 클래스 is kind of 인터페이스 는 성립되지 않으나, 하위 클래스가 ~를 할 수 있다 정도로 해석되는 경우
public interface Moveable{ void move(); } class Dog implements Moveable{ public void move(){ System.out.println("네발로 걷는다."); } } class Car implements Moveable{ public void move(){ System.out.println("바퀴로 움직인다."); } }
Java
복사
실제로 자바에서 공식적으로 제공하는 여러 인터페이스의 이름은 able 로 끝나는 경우가 많다.
Runnable, Cloneable, Serializable, Comparable …

사실상 대부분의 경우 인터페이스를 사용할 수 있다.

인터페이스에 default 메서드가 추가되어 추상클래스와 마찬가지로 인터페이스에 구체적으로 구현된 메서드를 사용할 수 있게 되었다. 비록 접근 제어자를 지정할 수 없다는 차이가 있으나 패키지에 따라 인터페이스의 특정 메서드를 사용할 수 없도록 할 이유를 생각하기 힘들다는 점에서 인터페이스의 접근 지정자를 default 로 선언하는 것으로 대체 할 수 있다.
만일 모든 하위 클래스에서 공통된 속성 즉 필드를 사용할 경우에는 여전히 추상 클래스를 사용해야 하지만, 객체지향 관점에서 속성은 온전히 객체가 알아서 다뤄야 하는 영역이고, 앞으로 모든 하위 클래스가 같은 속성을 사용해야 한다는 점이 제약으로 다가올 수 있으니 신중해야 할 것이다.
하지만, JPA의 엔티티 처럼 그 용도상 속성이 명확히 고정되는 경우에는 추상 클래스를 사용하는 것이 도움이 될 수 있다.