2025. 5. 28. 18:11ㆍJAVA/JAVA 실행 원리
자바에서 추상 클래스는 공통 로직을 정의하면서도 자식 클래스에게 특정 기능을 강제할 수 있는 설계 도구다. 객체 지향 설계의 중요한 원칙을 반영하는 요소이기도 하다.
🏗️ 추상 클래스란?
추상 클래스는 abstract 키워드로 정의되는 불완전한 클래스 입니다. 인스턴스화할 수없으며, 자식 클래스가 반드시 완성해야 하는 메서드(추상 메서드)를 포함할 수 있다.
추상클래스의 목적으로 일관된 인터페이스를 제공하면서, 기본 동작은 미리 구현해둔 클래스 템플릿 역할을 한다.
🌐 객체지향적 관점에서의 추상 클래스
추상 클래스는 "설계"다.
객체지향 프로그래밍에서 클래스는 단순히 코드를 담는 그릇이 아니고, 현실 세계의 개념과 관계를 추상화한 설계 청사진이다. 추상 클래스는 그중에서도 불완전한 설계로서, 상속을 통해 구체화되는 것을 전제로 한다.
즉, 추상 클래스는 "완성된 객체"가 아니라 "이런 객체가 필요하다"는 설계 명세를 나타낸다.
🔸 SOLID 원칙과의 연관성
1. 단일 책임 원칙(SRP : Single Responsibility Principle)
추상 클래스는 공통 기능을 하나의 상위 클래스로 분리하여, 책임을 명확하게 나눌 수 있게 해준다.
abstract class DocumentProcessor {
abstract void parse();
abstract void validate();
void save() {
System.out.println("Document saved.");
}
}
→ 문서 처리에 대한 "공통 로직"은 여기서 관리하고, 구체적인 파싱은 자식 클래스에서 맡는다.
2. 개방/폐쇄 원칙(OCP : Open/Close Principle)
추상 클래스는 변경 없이 확장(extend)을 가능하게 한다.
기존 코드는 그대로 두고, 새로운 클래스만 추가하면 된다.
class PdfProcessor extends DocumentProcessor {
void parse() { /* PDF 파싱 */ }
void validate() { /* PDF 유효성 검사 */ }
}
→ 기존 DocumentProcessor를 수정하지 않고도 기능 확장이 가능
3. 리스코프 치환 원칙(LSP : Liskov Substitution Principle)
자식 클래스는 언제나 부모 클래스(추상 클래스)로 대체 가능해야 한다.
void processDocument(DocumentProcessor processor) {
processor.parse();
processor.validate();
processor.save();
}
→ PdfProcessor, XmlProcessor 등 어떤 하위 클래스든 DocumentProcessor 타입으로 동작 가능해야 함
🔸 추상 클래스는 "계층적 설계"에 적합
"추상 클래스는 계층적 설계에 적합하다"는 의미는, 상속 계층 구조를 통해 공통 기능을 상위 클래스에 정의하고, 하위 클래스는 이를 확장 및 구체화하는 방식이 효과적이라는 뜻이다.
아래 예제는 이 개념을 명확히 보여준다.
🌏 상위 추상 클래스 Animal
// 상위 추상 클래스 : 공통 속석와 동작 정의
public abstract class Animal {
protected String name;
protected String sound;
public Animal() {}
public Animal(String name, String sound) {
this.name = name;
this.sound = sound;
}
// 공통 메서드
public void state() {
System.out.println(name + "의 울음소리는 " + sound);
}
// 추상 메서드 : 울음소리는 동물마다 다르다(하위 클래스가 바드시 구현)
public abstract void eat();
}
🐯 하위 클래스1 : Tiger
public class Tiger extends Animal {
public Tiger() {}
public Tiger(String name, String sound) {
this.name = name;
this.sound = sound;
}
@Override
public void eat() {
System.out.println(name + "는 고기를 좋아한다");
}
}
🐱 하위 클래스 2: Cat
public class Cat extends Animal {
public Cat() {}
public Cat(String name, String sound) {
this.name = name;
this.sound = sound;
}
@Override
public void eat() {
System.out.println(name +"는 츄르를 좋아한다");
}
}
⏯️ Main 메서드
public class Main {
public static void main(String... args) {
// 상위 타입으로 하위 클래스 참조(다형성)
Animal tiger = new Tiger("호랑이", "어흥");
Animal cat = new Cat("고양이", "야옹");
tiger.state(); // 호랑이의 울음소리는 어흥
tiger.eat(); // 호랑이는 고기를 좋아한다
cat.state(); // 고양이의 울음소리는 야옹
cat.eat(); // 고양이는 츄르를 좋아한다
}
}
📊 구조 요약 : 계층 설계 다이어 그램

💡 핵심 포인트
| 설계 요소 | 설명 |
| 상위 클래스 | Animal: 공통 속성과 동작 제공 (state) |
| 추상 메서드 | eat() : 하위 클래스가 개별 구현 |
| 하위 클래스 | Tiger, Cat : 각각의 고유한 동작 정의 |
| 다형성 | Animal 타입으로 Tiger, Cat 모두 다룰 수 있음 |
🌐 추상 클래스와 인터페이스의 차이(이론적)
| 항목 | 추상 클래스(abstract class) | 인터페이스(interface) |
| 정의 | 일부 구현을 포함할 수 있는 불완전한 클래스 | 메스드의 명세(계약)만 정의하는 구조적 틀 |
| 역할 | "공통 기능 + 부분 구현 제공" | "무언가 할 수 있음"이라는 능력 정의 |
| 철학 | 클래스의 계층적 확장 | 역할, 기능, 책임의 명세화 |
| 기본 목적 | 코드 재사용 + 설계 템플릿 제공 | 다형성 중심의 API 설계 |
🧱 추상 클래스의 철학 : "구조와 재사용 중심"
"나는 어떤 객체의 구체화된 일종이다"
"공통 속석와 동작이 있는 상위 계층을 물려받는다"
- 계층적 설계에 기반(상속 구조)
- 공통된 데이터(field)와 행위(method)를 정의
- 코드 재사용이 주요 목적
- 설계자가 상속 구조를 강하게 통제하고 싶을 때 사용
abstract class Animal {
String name;
void eat() { ... }
abstract void makeSound();
}
class Dog extends Animal { ... }
→ "Dog는 Animal의 일종이다."
→ Animal이 가진 구조와 동작을 물려받음
🎭 인터페이스 철학 : "행동과 계약 중심"
"나는 어떤 역할을 수행할 수 있다."
"이 기능을 반드시 제공해야 한다는 약속이다."
- 기능 중심 설계에 기반(역할 분리)
- 상태 없이, 순수하게 동작 명세만 존재
- 다형성과 유연한 구조 확장을 위해 설계
- 다양한 객체가 같은 기능을 "할 수 있도록" 표현의 자유 제공
interface Flyable {
void fly();
}
class Bird implements Flyable { ... }
class Airplane implements Flyable { ... }
→ "Bird와 Airplane은 모두 Flyable이다."
→ 완전한 다른 구조지만, 같은 역할을 수행할 수 있음
🌐 메모리 및 실행 관점에서의 추상 클래스
추상 클래스도 결국은 클래스다
자바에서 추상 클래스도 하나의 클래스이므로, 컴파일 시 .class 파일로 생성되고 JVM에 의해 로드, 메타정보로 관리된다
단, 추상클래스는 인스턴스를 만들 수 없지만, 정적 멤버 호출이나 타입 캐스팅 용도로 사용 가능하다.
추상 메서드는 바이트코드 상에서 구현체 없이 선언만 존재한다.
자식 클래스가 추상 메서드를 구현하지 않으면, 자식 클래스도 추상 클래스가 되어야 한다.
⚠️ 사용시 주의점
- 추상 클래스는 설계 계층을 깊게 만들 수 있으므로, 필요할 때만 사용
- 상속이 복잡한 경우에는 합성(composition)을 고려하는 것이 바람직
- 모든 클래스가 상속받을 필요가 없다면, 인터페이스로의 전환을 검토해야 한다.
🧭 결론
언제 추상 클래스를 써야 할까?
- "이 클래스는 절대 직접 인스턴스화되지 않아야 한다."
- "하위 클래스에게 일부 구현을 강제하고 싶다."
- "여러 클래스에 공통 로직을 제공하고 싶다."
- "상속 구조가 자연스럽고 'is-a' 관계가 성립한다."
'JAVA > JAVA 실행 원리' 카테고리의 다른 글
| Fully Qualified Name (1) | 2025.04.30 |
|---|---|
| String (1) | 2025.04.30 |
| Semantic Error와 Syntax Error (0) | 2025.04.23 |
| 2의 보수 (0) | 2025.04.16 |
| JVM(자바 가상 머신) (0) | 2025.03.28 |