추상클래스와 인터페이스
2025. 2. 25. 17:42ㆍJava/객체지향
1. 추상클래스 (Abstract Class)
1-1. 추상메소드
// 추상 메소드의 구현: 리턴타입 앞에 abstract키워드 붙이기
public abstract void method(); // 추상메소드는 반드시 끝에 세미콜론을 붙여 주어야 한다.
- 추상 메소드: 선언부만 작성하고 구현부 {}는 작성하지 않은 채로 남겨둔 메소드
- 설계만 해놓고 실제 수행될 내용은 작성하지 않았기 때문에 미완성 메소드이다.
- 메소드를 미완성 상태로 남겨 놓는 이유는 메소드의 내용이 소속받는 클래스에 따라 달라질 수 있기 때문에 클래스에서는 선언부만 작성하고, 실제 내용은 상속받는 클래스에서 구현하도록 비워두는 것이다.
- 자식클래스에서 클래스 내용을 구현하지 않으면 인스턴스가 생성되지 않으므로 내용 구현에 강제성을 줄 수 있다
1-2. 추상클래스
//class 앞에 abstract 키워드 붙이기
//추상 메소드를 하나라도 포함하면 반드시 추상 클래스가 된다.
abstract class Animal {
abstract void makeSound(); // 추상 메서드 (구현 X)
public void method(){} // 추상 클래스에는 추상메소드가 아닌 일반적인 필드, 메서드, 생성자도 존재 가능
}
//상속
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("멍멍!"); // 반드시 오버라이딩해야 함
}
}
public class Main {
public static void main(String[] args) {
Animal dog = new Dog();
dog.makeSound(); // "멍멍!" 출력
}
}
- 추상 클래스는 하위 클래스들의 공통점들을 모아 추상화하여 만든 클래스
- 추상화: 클래스 간의 공통점을 찾아내서 공통의 부모을 만드는 작업
- 구체화: 상속을 통해 구체적인 클래스를 구현하고 확장하는 작업
- 클래스A, B, C가 3가지의 공통점이 있으면 그것을 뽑아내서 추상클래스 X를 만든다.
- 공통점은 추상클래스 X를 상속받고 그 외의 부분은 자신의 클래스에 맞게 구현하여 완성해야만 한다.
- 추상 클래스는 추상 메소드를 포함하고 있는 불완전한 클래스
- 추상클래스로는 스스로 자신의 생성자를 활용한 인스턴스 생성이 불가능.
- 따라서 상속을 활용해 하위 클래스 타입의 인스턴스를 이용해서 인스턴스를 생성해야 한다.
1-3. 추상 클래스 사용 이유
- 공통 멤버 통합으로 중복 제거
필수 기능을 정의해 일관된 인터페이스(동일 기능)를 제공함에 있어 도움이 된다. - 구현의 강제성을 통한 기능 보장
추상 메소드를 포함한 추상 클래스는 추상 메소드를 통해 자식 클래스에 오버라이딩에 대한 강제성을 부여할 수 있다. (추상 클래스를 상속받는 자식 클래스는 반드시 추상 메소드를 오버라이딩 해야 한다.) - 규격에 맞는 설계 구현
실제 프로젝트에서 어플리케이션 아키텍쳐가 설계해 놓은 추상 클래스를 상속받으면, 개발자는 프로젝트에서 필요하고 공통적으로 들어가야하는 필드와 메서드를 오버라이딩해서 큰 설계를 생각할 필요 없이 구현만 하면 된다. 이렇게 하면 초기 설계 시간이 절약되고, 구현에만 집중할 수 있게 된다는 장점이 있다.
2. 인터페이스 (Interface)
2-1. 인터페이스
- 추상 메소드와 상수 필드만 가질 수 있는 클래스의 변형체 (추상 메소드의 집합)
- 인터페이스 또한 일종의 추상클래스이며 추상클래스보다 추상화정도가 높다.
- 추상클래스는 추상 메소드를 하나라도 가지고있으면 추상클래스이고 일반 메소드와 멤버변수를 가질 수 있지만,
인터페이스는 오직 추상 메소드와 상수만을 멤버로 가질 수 있고 그 외에는 다른 어떠한 요소도 허용하지 않는다. - 추상클래스가 '미완성 설계도' 라면, 인터페이스는 '기본 설계도' 라고 할 수 있다.
2-2. 인터페이스의 작성 - interface 키워드
- class 대신 interface라는 키워드를 사용하여 선언
- 모든 멤버변수는 상수 - public static final이어야 하며, 이를 생략할 수 있다.
- 모든 메소드는 추상메소드 - public abstract이어야 하며, 이를 생략할 수 있다.
- 단, Java 8 이후부터 default 메서드와 static 메서드도 가질 수 있음
interface Animal {
// 모든 필드는 public static final만 가능하다.
public static final PI = 3.1415;
// public static final을 생략해도 자동으로 작성된다.(생략 가능)
PI2 = 3.14;
// 기본적으로 메소드는 public abstract여야 한다.
public abstract void makeSound();
// public abstract를 생략해도 자동으로 작성된다.(생략 가능)
//void makeSound();
}
2-3. 인터페이스의 구현 - implements 키워드
- extends 대신 implementss라는 키워드를 사용하여 구현
- 인터페이스는 인터페이스로부터만 상속받을 수 있음
- 클래스와 달리 다중구현이 가능하다. class Dog implements Animal, Animal2, ..
- 인터페이스도 추상클래스처럼 당연히 그 자체로는 인스턴스를 생성할 수 없다.
추상클래스가 상속을 통해 추상 메소드를 완성하는 것 처럼, 인터페이스도 자신에 정의된 추상 메소드의 몸통을 만들어주는 '클래스'를 작성해야한다.
class Dog implements Animal { // 인터페이스 구현
@Override //오버라이딩 필수
public void makeSound() {
System.out.println("멍멍!");
}
}
public class Main {
public static void main(String[] args) {
Animal dog = new Dog();
dog.makeSound(); // "멍멍!" 출력
}
}
2-4. 인터페이스 사용 이유
- 인터페이스를 사용하면 상속에 얽매이지 않고 더 유연한 객체지향 설계가 가능해진다.
- 또한 자바의 단일 상속이라는 단점을 어느 정도 극복하기 위해 사용되기도 하며 모든 클래스는 하나의 부모 클래스 외에도 여러 개의 인터페이스를 구현할 수 있다.
- 표준화가 가능 -> 개발시간 단축
인터페이스는 공유를 목적으로 하는 상수를 기반으로 모든 기능을 공통화해서 강제성을 부여할 목적으로 만들어 졌다. (공통된 인터페이스)
3. 추상 클래스 vs 인터페이스 차이점 정리
구분 | 추상 클래스 | 인터페이스 |
사용 목적 | 공통 기능을 가진 클래스들의 상속을 위한 베이스 클래스 | 특정 기능을 정의하고 여러 클래스에서 구현할 때 |
자체 인스턴스 생성 | 불가 | 불가 |
다형성 적용시 상위타입 활용 가능 유무 | 가능 | 가능 |
상속 키워드 | extends | implements |
상속 방식 | 단일 상속만 가능 | 다중 구현 가능 |
메서드 | 추상 메서드 + 일반 메서드 |
기본적으로 추상 메서드만 (Java 8부터 default/static 메서드 가능) |
abstract 키워드 명시 | 명시적 | 묵시적 |
필드 (변수) | 인스턴스 변수 가질 수 있음 | public static final 상수만 가짐 |
생성자 | 생성자 가질 수 있음 | 생성자 없음 |
추상 클래스 사용
- "is-a" 관계일 때 (예: Animal → Dog)
- 공통적으로 필요한 필드나 메서드가 있을 때
- 기본 구현을 포함한 클래스를 만들고 싶을 때
인터페이스 사용
- "can-do" 관계일 때 (예: Flyable → Bird, Airplane)
- 여러 클래스에서 공통 기능을 제공해야 할 때 (구현 방식이나 대상에 대해 추상화)
- 다중 구현을 활용하고 싶을 때
4. 인터페이스를 사용하면 유연성이 생기는 이유
4-1. 상속만 이용하는 경우의 문제점 (강한 결합)
// 추상 클래스 (조상 클래스)
abstract class Creature {
abstract void swimming(); // 수영 동작을 하는 추상 메소드
}
// 추상 클래스 (부모 클래스)
abstract class Animal extends Creature { }
abstract class Fish extends Creature { }
// 자식 클래스
class Parrot extends Animal {
// 앵무새는 수영을 할수 없지만 상속 관계로 인해 강제적으로 메소드를 구현해야하는 사태가 일어난다.
void swimming() {}
}
class Tiger extends Animal {
// 호랑이는 수영을 할수 없지만 상속 관계로 인해 강제적으로 메소드를 구현해야하는 사태가 일어난다.
void swimming() {}
}
class People extends Animal {
void swimming() { //... }
}
class Whale extends Fish {
void swimming() { // ... }
}
- Creature라는 추상 클래스에 추상 메소드를 추가하면, 곧 이를 상속하는 모든 자손/자식 클래스에서 반드시 메소드를 구체화 해야한다는 규칙 때문에 실제로 수영을 못하는 호랑이(Tiger)와 앵무새(Parrot) 클래스에서도 메소드를 구현해야 하는 강제성이 생기게 된다.
- 물론 메소드를 선언하기만 하고 빈칸으로 놔두면 되기는 하지만, 이는 객체 지향 설계에 위반될 뿐만 아니라 나중에 유지보수 면에서도 마이너스 적인 효과가 된다.
4-2. 인터페이스를 활용하여 유연하게 객체지향 설계 (약한 결합)
- 인터페이스는 상속에 얽매히지 않는다. 인터페이스에 추상 메소드를 선언하고 이를 구현(implements) 하면서 자유로운 타입 묶음을 통해 추상화를 이루게 한다.
- 날아 다니는 동작 메서드나, 말하는 동작 메서드를 각각 인터페이스마다 분리하여 선언하고 이를 각 자식 클래스에 자유롭게 상속시킴으로써 보다 구조화된 객체 지향 설계를 추구 할 수 있는 것이다.
//추상화 클래스로 상속
abstract class Creature { } //부모 클래스를 추상클래스로 선언
abstract class Animal extends Creature { } //자식 클래스가 이를 상속
abstract class Fish extends Creature { }
//인터페이스로 기능 추상화
interface Flyable {
void flying();
}
interface Talkable {
void talking();
}
interface Swimmable {
void swimming();
}
//인터페이스를 통해 상속 계층에 얽매이지 않고 필요한 기능만 조합해서 구현 가능
class Tiger extends Animal { }
class Parrot extends Animal implements Talkable{
@Override
public void talking() {}
}
// 필요에 따라 적재적소에 다중으로 여러개 추가(구현)이 가능함
class People extends Animal implements Talkable, Swimmable{
@Override
public void talking() {}
@Override
public void swimming() {}
}
class Whale extends Fish implements Swimmable{
@Override
public void swimming() {}
}
4-3. 인터페이스 구현 장점 정리
1) 다중 구현이 가능 → 여러 역할을 가질 수 있음
- 자바는 단일 상속만 가능하지만, 인터페이스는 여러 개를 동시에 구현할 수 있다.
즉, 클래스가 특정 부모 클래스 하나에 종속되지 않고, 다양한 기능을 조합할 수 있다. - People 클래스는 Talkable(말하는 기능)과 Swimmable(헤엄치는 기능)을 동시에 가질 수 있다.
만약 추상 클래스로 상속을 했다면, 하나의 클래스만 상속 가능하기 때문에 이런 유연성이 떨어진다.
2) 특정 기능(행동)을 분리할 수 있음
- 인터페이스는 행동(기능)을 정의하는 용도로 쓰이기 좋다,
- 상속을 쓰면 부모-자식 관계가 형성되지만,
인터페이스를 쓰면 특정 행동만 따로 분리해서 원하는 클래스에 붙일 수 있다.
3) 상속 트리가 복잡해지는 걸 방지할 수 있음
- 상속을 많이 쓰면 부모-자식 관계가 깊어지고, 유지보수가 어려워질 수 있다.
인터페이스를 활용하면 기능을 조합하는 방식으로 개발할 수 있어서 상속 트리가 단순해짐 - 위의 예시처럼 Creature라는 추상 클래스에 Swimmable() 추상 메소드를 추가하면, 모든 인스턴스들이 무조건 수영을 할 수 있어야하는 문제가 생길 수 있다. 하지만 인터페이스를 사용하면 기능을 따로 조합할 수 있다.
- 이렇게 하면 포유류/조류와 상관없이 원하는 동물에게 특정 기능만 부여할 수 있다.
불필요한 상속 트리를 줄이고, 기능을 조합하는 방식으로 개발 가능!
4-4. 결론: 인터페이스가 객체지향 설계를 자유롭게 만든다.
방식 | 특징 | 문제점 |
상속 (extends) | 공통된 기능을 재사용 | 단일 상속만 가능, 부모-자식 관계가 강하게 결합됨 |
인터페이스 (implements) | 기능을 조합해서 다중 구현 가능 | 모든 메서드가 추상 메서드라 기본 구현이 필요 |
인터페이스를 사용하면 -> 상속 계층에 얽매이지 않고 필요한 기능만 조합해서 개발할 수 있다
- 클래스 간의 강한 결합을 줄이고(느슨한 결합), 재사용성이 높아짐
- 기능 단위로 설계가 가능해서, 객체지향 설계를 더 유연하게 할 수 있음
- 유지보수가 쉬워지고, 새로운 기능을 추가해도 기존 구조를 크게 변경할 필요가 없다.
참고한 포스팅
☕ 인터페이스 vs 추상클래스 용도 차이점 - 완벽 이해
인터페이스 vs 추상클래스 비교 이 글을 찾아보는 독자분들은 아마도 이미 인터페이스와 추상클래스 개념을 학습한 뒤에 이 둘에 대하여 차이의 모호함 때문에 방문 했겠지만, 그래도 다시한번
inpa.tistory.com