3주차 Unit 4.1 — 추상클래스의 특징

Psj·2026년 5월 19일

F-lab

목록 보기
88/239

Unit 4.1 — 추상클래스의 특징

F-LAB JAVA · 3주차 · Phase 4 · 추상화의 두 도구
🚀 Phase 4 시작 — 추상클래스 vs 인터페이스 정복


📌 학습 목표

이 Unit을 끝내면 다음을 답할 수 있어야 한다.

  • abstract 키워드 의 정확한 의미는?
  • 추상클래스일반 클래스 의 결정적 차이는?
  • 추상 메서드구현 메서드 가 한 클래스에 공존하는 의미는?
  • 추상클래스의 생성자 는 왜 호출 가능한가? (직접 인스턴스화는 불가능한데)
  • 단일 상속 (Single Inheritance) 제약과 그 이유는?
  • abstract + final 의 조합이 왜 컴파일 에러인가?
  • Template Method 패턴 과 추상클래스의 관계는?
  • 자바 표준 라이브러리의 AbstractList, AbstractMap 의 역할은?
  • 추상클래스의 7가지 제약 과 그 의도는?

🎯 핵심 한 문장

추상클래스는 "공통 동작은 구현하고, 변하는 부분은 자식에게 위임" 하는 추상화 도구다.
abstract 키워드로 표시되고, 직접 인스턴스화는 불가능 하며 자식 클래스가 추상 메서드를 모두 구현해야 사용 가능.
공통 코드 재사용 + 강제된 확장 지점 의 균형을 제공.
Template Method 패턴의 토대이며, AbstractList, HttpServlet 등 자바 라이브러리의 핵심 설계.

비유 — 건물 설계도와 시공

일반 클래스 (Concrete Class):
  완성된 건물 → 바로 입주 가능
  new House() → 즉시 사용

추상클래스 (Abstract Class):
  부분 설계도 → 일부는 완성, 일부는 비워둠
  - 거실 구조: 완성 (구현 메서드)
  - 외벽 자재: 비워둠 (추상 메서드, 자식이 결정)
  
  직접 입주 X → 자식이 빈 곳 채워야 입주
  
  예: AbstractHouse (거실 구현) → KoreanHouse (한옥 자재 선택) → 입주

→ 추상클래스 = 부분 구현 + 강제 확장.


🧭 9개 섹션 로드맵

1. abstract 키워드의 정의와 역할
2. 추상클래스의 본질 — 부분 구현
3. 추상 메서드의 문법과 의미
4. 추상클래스의 생성자
5. 단일 상속 제약과 그 이유
6. 7가지 제약과 컴파일 에러
7. Template Method 패턴
8. 자바 표준 라이브러리의 활용
9. 면접 + 자기 점검

1️⃣ abstract 키워드의 정의와 역할

1.1 abstract 의 두 가지 사용

abstract 키워드는 두 곳에 사용:

1. 클래스 앞에:
   abstract class AbstractShape { ... }
   
   의미: "이 클래스는 추상이라 직접 인스턴스화 못 함"

2. 메서드 앞에:
   abstract void draw();
   
   의미: "이 메서드는 구현 없음, 자식이 반드시 구현"

1.2 abstract 의 본질

abstract = "불완전함"

불완전한 클래스 = 일부 메서드가 구현 안 됨
  → 인스턴스 생성 불가
  → 자식이 완성해야 함

불완전한 메서드 = 시그니처만 있음
  → 본체 없음
  → 자식이 정의해야 함

1.3 기본 문법

// 추상클래스 정의
public abstract class Shape {
    
    // 인스턴스 필드 (가능)
    protected String name;
    
    // 생성자 (가능, 자식에서 호출)
    protected Shape(String name) {
        this.name = name;
    }
    
    // 구현 메서드 (가능)
    public String getName() {
        return name;
    }
    
    // 추상 메서드 (자식 강제 구현)
    public abstract double area();
    public abstract double perimeter();
    
    // 구현 메서드에서 추상 메서드 사용 (Template Method)
    public String describe() {
        return name + ": area=" + area() + ", perimeter=" + perimeter();
    }
}

// 자식 클래스
public class Circle extends Shape {
    private double radius;
    
    public Circle(double radius) {
        super("Circle");
        this.radius = radius;
    }
    
    @Override
    public double area() {
        return Math.PI * radius * radius;
    }
    
    @Override
    public double perimeter() {
        return 2 * Math.PI * radius;
    }
}

// 사용
// Shape s = new Shape("X");   // ❌ 컴파일 에러
Shape circle = new Circle(5);   // ✓ 자식으로 생성
circle.describe();              // ✓ 상속받은 메서드 사용

1.4 추상클래스의 5가지 가능한 멤버

public abstract class Animal {
    
    // 1. 필드 (인스턴스 + static)
    protected String name;
    private static int count = 0;
    
    // 2. 생성자
    protected Animal(String name) {
        this.name = name;
        count++;
    }
    
    // 3. 구현 메서드 (concrete method)
    public String getName() {
        return name;
    }
    
    // 4. 추상 메서드 (abstract method)
    public abstract String sound();
    
    // 5. static 메서드
    public static int getCount() {
        return count;
    }
}

핵심:

  • 일반 클래스의 모든 것 + 추상 메서드
  • 추상 메서드가 0개도 가능 (그래도 인스턴스화 불가)

1.5 추상 메서드 없는 추상클래스

public abstract class EmptyAbstract {
    
    private int value;
    
    public EmptyAbstract(int value) {
        this.value = value;
    }
    
    public int getValue() {
        return value;
    }
    
    // 추상 메서드 0개
}

// EmptyAbstract a = new EmptyAbstract(10);   // ❌ 컴파일 에러

abstract 키워드만으로도 인스턴스화 불가.
사용처:

  • 자식만 사용하길 강제
  • 추상 메서드는 나중에 추가 예정

1.6 추상클래스 vs 일반 클래스

항목추상클래스일반 클래스
키워드abstract classclass
인스턴스화❌ 불가✓ 가능
추상 메서드가능불가
생성자가능 (자식 호출용)가능
필드가능가능
다중 상속❌ (단일 상속)❌ (단일 상속)
final 조합❌ 에러가능

1.7 자기 점검 답변

abstract 키워드가 클래스와 메서드에서 의미가 다른가?

:

  • 공통 의미: "불완전함"
  • 클래스 abstract:
    • 직접 인스턴스화 불가
    • 자식이 완성해야 사용 가능
    • 추상 메서드 0개도 가능
  • 메서드 abstract:
    • 본체 없음 (시그니처만)
    • 자식이 반드시 구현
    • 추상클래스 안에서만 선언 가능

→ 함께 사용되지만 별도 개념.


2️⃣ 추상클래스의 본질 — 부분 구현

2.1 추상클래스의 존재 이유

일반 클래스만 있다면:
  - 모든 기능을 직접 구현
  - 비슷한 클래스에 중복 코드
  - 변경 시 모든 곳 수정

인터페이스만 있다면:
  - 구현 X (Java 7 까지)
  - 메서드 강제만 가능
  - 공통 코드 재사용 X

추상클래스의 해결:
  - 공통 코드는 부모에서 구현
  - 변하는 부분만 자식에서 구현
  - 코드 재사용 + 유연성

2.2 부분 구현의 시각화

일반 부모 클래스:
  ┌─────────────────────────┐
  │ class Animal             │
  │   - name (구현)           │
  │   - eat() (구현)          │
  │   - sound() (구현)        │ ← 모든 동물 같은 소리?
  └─────────────────────────┘
  
문제: sound() 가 동물마다 달라야 함

추상클래스:
  ┌─────────────────────────┐
  │ abstract class Animal    │
  │   - name (구현)           │
  │   - eat() (구현)          │
  │   + sound() (추상)        │ ← 자식이 결정!
  └─────────────────────────┘
       │
       ├──→ Dog: sound() → "Bark!"
       ├──→ Cat: sound() → "Meow!"
       └──→ Cow: sound() → "Moo!"

2.3 코드 예시 — ILIC 운송 계산

// 운송수단별 요금 계산
public abstract class ShipmentMethod {
    
    protected String name;
    protected BigDecimal baseRate;
    
    protected ShipmentMethod(String name, BigDecimal baseRate) {
        this.name = name;
        this.baseRate = baseRate;
    }
    
    // 공통 구현: 모든 운송수단이 같은 로직
    public final BigDecimal calculateTotalFare(int weight, int distance) {
        BigDecimal base = baseRate.multiply(BigDecimal.valueOf(weight));
        BigDecimal distanceFactor = calculateDistanceFactor(distance);
        BigDecimal fuelSurcharge = calculateFuelSurcharge(weight);
        return base.multiply(distanceFactor).add(fuelSurcharge);
    }
    
    // 추상 메서드: 운송수단마다 다른 로직
    protected abstract BigDecimal calculateDistanceFactor(int distance);
    protected abstract BigDecimal calculateFuelSurcharge(int weight);
}

// 해상 운송
public class SeaShipment extends ShipmentMethod {
    
    public SeaShipment() {
        super("Sea", new BigDecimal("0.50"));
    }
    
    @Override
    protected BigDecimal calculateDistanceFactor(int distance) {
        // 거리 1000km 마다 1.1배
        return BigDecimal.valueOf(1.0 + (distance / 1000.0) * 0.1);
    }
    
    @Override
    protected BigDecimal calculateFuelSurcharge(int weight) {
        // 해상은 무게당 고정 비율
        return BigDecimal.valueOf(weight).multiply(new BigDecimal("0.05"));
    }
}

// 항공 운송
public class AirShipment extends ShipmentMethod {
    
    public AirShipment() {
        super("Air", new BigDecimal("3.00"));
    }
    
    @Override
    protected BigDecimal calculateDistanceFactor(int distance) {
        // 항공은 거리 100km 마다 1.05배
        return BigDecimal.valueOf(1.0 + (distance / 100.0) * 0.05);
    }
    
    @Override
    protected BigDecimal calculateFuelSurcharge(int weight) {
        // 항공은 더 비싼 유가
        return BigDecimal.valueOf(weight).multiply(new BigDecimal("0.20"));
    }
}

// 사용
ShipmentMethod sea = new SeaShipment();
ShipmentMethod air = new AirShipment();

BigDecimal seaCost = sea.calculateTotalFare(1000, 5000);
BigDecimal airCost = air.calculateTotalFare(1000, 5000);

// 공통 calculateTotalFare 는 한 번만 작성
// 운송수단별 차이는 자식 클래스에서만

2.4 코드 재사용의 효과

일반 클래스로 구현 시:
  SeaShipment, AirShipment, GroundShipment 각각:
  - calculateTotalFare 모두 구현
  - 같은 로직 (base + distance + fuel) 반복
  - 변경 시 3곳 모두 수정

추상클래스로 구현 시:
  ShipmentMethod 의 calculateTotalFare 한 번
  - 모든 자식이 같은 로직 사용
  - 변경 시 한 곳만 수정
  - 각 자식은 변하는 부분만 작성

2.5 추상클래스의 의도

설계 의도:

1. "is-a" 강한 관계
   Circle is-a Shape
   SeaShipment is-a ShipmentMethod
   → 상속 관계 자연스러움

2. 공통 상태 + 공통 동작
   필드, 구현 메서드 공유
   인터페이스 (Java 8 이전) 와 다른 점

3. 확장 지점 강제
   추상 메서드 = "여기 채워야 함"
   자식이 구현 안 하면 컴파일 에러
   → 안전한 확장

4. 부분 캡슐화
   protected 멤버로 자식만 접근
   외부엔 비공개

2.6 추상클래스의 적합성

적합한 경우:
  ✓ 자식 클래스들이 많은 공통 코드 가짐
  ✓ "is-a" 관계가 자연스러움
  ✓ protected 상태/메서드 공유 필요
  ✓ Template Method 패턴 적용

부적합한 경우:
  ✗ "can-do" 능력만 표현 (인터페이스가 적합)
  ✗ 다중 분류 필요 (단일 상속 제약)
  ✗ 공통 코드 거의 없음
  ✗ 자식 클래스 수가 적음

2.7 자기 점검 답변

추상클래스가 일반 클래스 + 추상 메서드와 다른 의도는?

:
1. "부분 구현" 의 의도:

  • 일반 클래스: 완성된 구현
  • 추상클래스: 구현 + 빈 곳 (자식 채우기)
  1. 강제된 확장 지점:

    • 추상 메서드 = "여기 반드시 구현"
    • 자식이 누락 시 컴파일 에러
    • 안전한 확장 보장
  2. 공통 코드 재사용 + 유연성:

    • 공통은 부모에서
    • 변하는 부분은 자식에서
    • 변경 시 한 곳만 수정

3️⃣ 추상 메서드의 문법과 의미

3.1 추상 메서드의 문법

public abstract class Shape {
    
    // 추상 메서드:
    // - abstract 키워드
    // - 본체 없음 (세미콜론으로 끝)
    // - 접근 제어자 가능 (public, protected, package-private)
    // - private 불가 (자식이 override 못 함)
    // - final 불가 (override 강제와 충돌)
    // - static 불가 (인스턴스 메서드만)
    
    public abstract double area();
    
    protected abstract void render(Canvas canvas);
    
    abstract boolean isValid();   // package-private
    
    // private abstract void hidden();        // ❌ 에러
    // final abstract void cannot();          // ❌ 에러
    // static abstract void invalid();        // ❌ 에러
}

3.2 추상 메서드의 4가지 제약

1. 본체 없음
   - 세미콜론으로 끝
   - { } 또는 코드 작성 시 에러

2. private 불가
   - private 은 자식에서 접근 불가
   - 추상 메서드는 자식이 구현해야
   - 모순 → 에러

3. final 불가
   - final 은 override 금지
   - 추상 메서드는 override 강제
   - 모순 → 에러

4. static 불가
   - static 은 인스턴스 무관
   - 추상은 polymorphism 위한 것
   - 의미적 모순

3.3 자식의 구현 강제

public abstract class Shape {
    public abstract double area();
}

// 자식 1 — 구현
public class Circle extends Shape {
    private double radius;
    
    @Override
    public double area() {
        return Math.PI * radius * radius;
    }
}

// 자식 2 — 구현 안 함
public class IncompleteShape extends Shape {
    // ❌ 컴파일 에러:
    // The type IncompleteShape must implement the inherited 
    // abstract method Shape.area()
}

// 자식 3 — 자기도 추상
public abstract class AbstractCircle extends Shape {
    // 구현 안 해도 됨 (자기도 추상이라)
    // 다음 자식이 구현하면 됨
}

public class ConcreteCircle extends AbstractCircle {
    @Override
    public double area() {
        return 3.14;
    }
}

3.4 @Override 의 권장

public class Circle extends Shape {
    
    @Override   // ★ 권장
    public double area() {
        return Math.PI * radius * radius;
    }
}

@Override 의 효과:

  • 부모 메서드를 실제로 override 하는지 컴파일러 검증
  • 오타 (aera() 등) 즉시 발견
  • 코드 의도 명확화
  • IDE 의 도구 지원

3.5 추상 메서드 시그니처의 일치

public abstract class Shape {
    public abstract double area();
}

public class Circle extends Shape {
    
    // ✓ 올바른 override
    @Override
    public double area() { return ...; }
    
    // ❌ 시그니처 불일치 (이름 다름)
    @Override
    public double areaa() { return ...; }
    
    // ❌ 반환 타입 다름
    @Override
    public int area() { return 0; }
    
    // △ 매개변수 다름 → override 아닌 overload
    public double area(double scale) { return ...; }
    // 추상 메서드 area() 는 여전히 구현 안 됨 → 컴파일 에러
}

핵심:

  • 메서드명 + 매개변수 = 시그니처
  • 반환 타입은 시그니처 일부 아님 (변경 시 에러)
  • 정확한 시그니처로 override

3.6 covariant return type

자바 5+ 부터 자식이 반환 타입을 좁힐 수 있음:

public abstract class Shape {
    public abstract Shape clone();
}

public class Circle extends Shape {
    
    @Override
    public Circle clone() {   // ★ Shape 아닌 Circle 반환
        return new Circle(this.radius);
    }
}

// 사용 시 더 정확한 타입
Circle c = new Circle(5);
Circle cloned = c.clone();   // Shape 아닌 Circle 직접 반환

이를 공변 반환 타입 (Covariant Return Type) 이라 함.

3.7 protected 추상 메서드

public abstract class ShipmentMethod {
    
    // 외부에서 호출 가능
    public final BigDecimal calculateTotalFare(int weight, int distance) {
        // ...
        BigDecimal factor = calculateDistanceFactor(distance);   // 자기 호출
        // ...
    }
    
    // 자식만 구현, 외부 호출 X
    protected abstract BigDecimal calculateDistanceFactor(int distance);
}

protected abstract:

  • 자식이 구현
  • 외부에서 직접 호출 X
  • 공개 인터페이스 + 비공개 확장 지점

3.8 throws 처리

public abstract class FileProcessor {
    
    // 추상 메서드도 throws 선언 가능
    public abstract void process(File file) throws IOException;
}

public class TextFileProcessor extends FileProcessor {
    
    @Override
    public void process(File file) throws IOException {
        // ...
    }
    
    // 자식은 같은 또는 좁은 예외 (또는 unchecked)
    
    // ✓ 같은 예외
    // ✓ IOException 의 자식 (FileNotFoundException 등)
    // ✓ 예외 안 던짐도 가능
    // ❌ 새 checked 예외 추가는 불가
}

3.9 자기 점검 답변

추상 메서드의 4가지 제약과 그 이유는?

:
1. 본체 없음 — 자식이 구현하라는 의미
2. private 불가 — 자식이 접근/override 못 함, 모순
3. final 불가 — override 금지와 강제의 모순
4. static 불가 — polymorphism 무의미

추가:

  • 시그니처 정확히 일치 필수
  • @Override 권장
  • 공변 반환 타입 (Java 5+) 가능

4️⃣ 추상클래스의 생성자

4.1 생성자의 모순처럼 보이는 이유

추상클래스의 특징:
  - 직접 인스턴스화 불가
  - new AbstractShape() → 컴파일 에러

그런데 생성자는?
  - 보통 객체 생성 시 호출
  - 추상클래스 인스턴스화 불가 → 호출 안 됨?

해답:
  - 자식 클래스가 생성될 때 호출
  - super() 로 부모 생성자 호출
  - 부모의 필드 초기화에 활용

4.2 생성자 호출 흐름

public abstract class Shape {
    protected String name;
    
    protected Shape(String name) {   // 생성자
        this.name = name;
        System.out.println("Shape created: " + name);
    }
}

public class Circle extends Shape {
    private double radius;
    
    public Circle(double radius) {
        super("Circle");   // ★ 부모 생성자 호출
        this.radius = radius;
        System.out.println("Circle created with radius " + radius);
    }
}

// 사용
Circle c = new Circle(5);
// 출력:
// Shape created: Circle
// Circle created with radius 5

4.3 호출 순서의 메커니즘

new Circle(5) 호출 시:

Step 1: 메모리 할당 (Circle 크기)
Step 2: Object 의 생성자 시작
Step 3: Shape 의 생성자 시작
   - super("Circle") 명시되었음
   - name = "Circle"
Step 4: Shape 의 생성자 종료
Step 5: Circle 의 생성자 본체 시작
   - radius = 5
Step 6: Circle 의 생성자 종료
Step 7: new 반환 → Circle 객체

→ 위에서 아래로 (Object → Shape → Circle)

4.4 super() 의 명시적/암묵적 호출

public abstract class Shape {
    
    // 기본 생성자
    protected Shape() {
        System.out.println("Shape()");
    }
    
    // 매개변수 생성자
    protected Shape(String name) {
        this.name = name;
        System.out.println("Shape(name)");
    }
}

// 자식 1 — super() 명시
public class Circle extends Shape {
    public Circle() {
        super();   // 명시적
    }
}

// 자식 2 — super() 생략 → 컴파일러가 super() 자동 추가
public class Square extends Shape {
    public Square() {
        // super() 가 자동 추가됨 (기본 생성자가 있다면)
    }
}

// 자식 3 — 부모에 기본 생성자 없으면
public class Rectangle extends Shape {
    public Rectangle() {
        super("Rectangle");   // ★ 명시 필수
        // 또는
        // super();   // ❌ Shape 의 기본 생성자 없으면 에러
    }
}

핵심:

  • super() 가 첫 줄에 있어야 함
  • 생략 시 컴파일러가 super() 자동 추가
  • 부모에 기본 생성자 없으면 명시 필수

4.5 생성자의 접근 제어

public abstract class Shape {
    
    // ✓ public 가능
    public Shape() { ... }
    
    // ✓ protected 가능 (자식만 호출)
    protected Shape(String name) { ... }
    
    // ✓ package-private 가능
    Shape(int n) { ... }
    
    // ✓ private 가능 (드물게)
    private Shape(boolean special) { ... }
    // → 같은 클래스 내에서만 호출 (다른 생성자가 this() 로)
}

권장:

  • 추상클래스 생성자는 보통 protected
  • public 도 가능하지만 자식만 사용 → protected 가 의도 명확

4.6 생성자에서 추상 메서드 호출의 위험

public abstract class Shape {
    
    public Shape() {
        init();   // ❌ 위험!
    }
    
    public abstract void init();
}

public class Circle extends Shape {
    private double radius = 5;
    
    public Circle() {
        super();   // → Shape() → init() 호출
        radius = 10;
    }
    
    @Override
    public void init() {
        System.out.println("Radius: " + radius);
        // 어떤 값?
    }
}

// 출력:
// Radius: 0.0   ← 0!

이유:

  • Shape 의 생성자 실행 시점에서
  • Circle 의 필드 초기화는 아직 안 됨
  • radius 는 0.0 (기본값)
  • 그 후 Circle 의 생성자가 5 → 10 으로

해결:

  • 생성자에서 추상 메서드 호출 회피
  • 또는 자식 클래스가 부모 호출 후 초기화

4.7 다양한 생성자 패턴

public abstract class Animal {
    
    protected String name;
    protected int age;
    
    // 생성자 1: 기본
    protected Animal() {
        this("Unknown", 0);
    }
    
    // 생성자 2: 이름만
    protected Animal(String name) {
        this(name, 0);
    }
    
    // 생성자 3: 전체
    protected Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

public class Dog extends Animal {
    
    public Dog() {
        super();   // 또는 super("Dog", 0);
    }
    
    public Dog(String name, int age) {
        super(name, age);
    }
}

4.8 자기 점검 답변

추상클래스가 인스턴스화 안 되는데 생성자가 있는 이유는?

:
1. 자식 클래스가 super() 로 호출
2. 부모 필드 초기화에 활용
3. 부모의 invariant 설정 (제약 조건)
4. 공통 초기화 로직 재사용

호출 흐름:

  • new Circle() → Object() → Shape() → Circle()
  • 위에서 아래로

주의:

  • 생성자에서 추상 메서드 호출 X
  • 자식 필드 초기화 전 시점

5️⃣ 단일 상속 제약과 그 이유

5.1 자바의 단일 상속

// ❌ 다중 상속 불가
public class Circle extends Shape, Drawable {   // 에러!
}

// ✓ 단일 상속
public class Circle extends Shape {
}

// ✓ 인터페이스는 다중 가능
public class Circle extends Shape implements Drawable, Comparable {
}

5.2 다중 상속의 문제 — Diamond Problem

다중 상속을 허용한다면:

      Animal
      /    \
   Mammal  Bird
      \    /
     [Bat - 둘 다 상속]
     
문제:
  Mammal.move() 가 있고 Bird.move() 도 있다면
  Bat.move() 는 어느 것?
  
  → "Diamond Problem" — 모호함

5.3 자바의 선택 — 단일 상속

자바의 결정 (1995):
  - 클래스 다중 상속 ❌
  - 인터페이스 다중 구현 ✓ (구현이 없으니 모호함 없음)

이유:
  1. 단순성 — Diamond Problem 회피
  2. 명확한 메서드 해결 (method resolution)
  3. C++ 의 복잡한 다중 상속 회피

5.4 단일 상속의 영향

public abstract class Animal {
    public abstract void move();
}

public abstract class Swimmer {
    public abstract void swim();
}

// 문제: 물고기는 Animal 이자 Swimmer
public class Fish extends Animal {   // OK
    // extends Swimmer 도 못 함
    
    @Override
    public void move() { ... }
    
    // Swimmer 의 swim() 못 받음
}

해결 방안:
1. 인터페이스로: interface Swimmer + implements
2. 컴포지션: private Swimmer swimmer;
3. 계층 재구성: Animal → AquaticAnimal → Fish

5.5 인터페이스의 등장

// 1번째 부모 클래스
public abstract class Animal {
    public abstract void move();
}

// 능력을 인터페이스로
public interface Swimmer {
    void swim();
}

public interface Flyer {
    void fly();
}

// 다중 구현 가능
public class Bat extends Animal implements Flyer {
    @Override
    public void move() { ... }
    
    @Override
    public void fly() { ... }
}

public class Fish extends Animal implements Swimmer {
    @Override
    public void move() { ... }
    
    @Override
    public void swim() { ... }
}

public class Duck extends Animal implements Swimmer, Flyer {
    @Override
    public void move() { ... }
    
    @Override
    public void swim() { ... }
    
    @Override
    public void fly() { ... }
}

→ Phase 4 의 핵심 메시지: 클래스는 단일 상속, 인터페이스는 다중 구현.

5.6 자바 8+ default 메서드의 등장

public interface Swimmer {
    default void swim() {   // 구현 가능
        System.out.println("Swimming");
    }
}

public interface Flyer {
    default void fly() {
        System.out.println("Flying");
    }
}

// 다중 default 의 충돌?
public interface SuperHero extends Swimmer, Flyer {
    @Override
    default void swim() {
        System.out.println("Super swim");
    }
}

Java 8+ 에선 인터페이스에도 구현 가능 → diamond problem 부분 부활.
다음 Unit 4.3 에서 자세히.

5.7 단일 상속의 우회 패턴

패턴 1: 컴포지션 (Composition)

public class Duck extends Animal {
    private Swimmer swimmer = new BasicSwimmer();
    private Flyer flyer = new BasicFlyer();
    
    public void swim() { swimmer.swim(); }
    public void fly() { flyer.fly(); }
}

패턴 2: 위임 (Delegation)

public class Duck extends Animal implements Swimmer, Flyer {
    private Swimmer swimmer;
    private Flyer flyer;
    
    public Duck(Swimmer s, Flyer f) {
        this.swimmer = s;
        this.flyer = f;
    }
    
    @Override
    public void swim() { swimmer.swim(); }
    
    @Override
    public void fly() { flyer.fly(); }
}

패턴 3: Mixin (자바엔 없음)

Scala, Ruby 등의 mixin:
  - 여러 trait/module 을 합성
  - 자바는 인터페이스 + default 로 부분적 흉내

5.8 자기 점검 답변

자바가 단일 상속을 채택한 이유와 우회 방법은?

:

  • 이유:

    • Diamond Problem 회피
    • 명확한 메서드 해결
    • C++ 의 복잡함 회피
    • 단순성 우선
  • 우회 방법:

    1. 인터페이스 다중 구현 — 능력 표현
    2. 컴포지션 — has-a 관계
    3. 위임 — 메서드를 다른 객체에 위임
    4. 계층 재구성 — 상속 트리 재설계

→ "is-a 는 단일 상속, can-do 는 다중 구현".


6️⃣ 7가지 제약과 컴파일 에러

6.1 제약 1 — 인스턴스화 불가

public abstract class Shape { ... }

// ❌ 에러
Shape s = new Shape();

// ✓ 자식으로 가능
Shape s = new Circle();

// ✓ 익명 자식 클래스 (사실 자식)
Shape s = new Shape() {
    @Override
    public double area() { return 10; }
};

6.2 제약 2 — abstract + final 불가

// ❌ 에러
public abstract final class Shape { ... }

// 이유:
// - abstract: 자식이 완성해야 함
// - final: 자식 못 만듦
// - 모순!

6.3 제약 3 — abstract + private 불가 (메서드)

public abstract class Shape {
    
    // ❌ 에러
    private abstract void hidden();
    
    // 이유:
    // - abstract: 자식이 override 해야 함
    // - private: 자식에서 접근 불가
    // - 모순!
}

6.4 제약 4 — abstract + static 불가 (메서드)

public abstract class Shape {
    
    // ❌ 에러
    public static abstract void doSomething();
    
    // 이유:
    // - static: 클래스 메서드 (인스턴스 무관)
    // - abstract: polymorphism 통한 자식 동작
    // - 모순!
}

6.5 제약 5 — abstract 메서드는 abstract 클래스 안에서만

// 일반 클래스
public class Circle {
    
    // ❌ 에러
    public abstract void draw();
    
    // 이유:
    // - 추상 메서드 있는 클래스는 추상클래스여야
    // - 일반 클래스로 만들 수 없음
}

// ✓ 추상클래스에서만 가능
public abstract class Shape {
    public abstract void draw();
}

6.6 제약 6 — abstract 메서드는 본체 없음

public abstract class Shape {
    
    // ❌ 에러
    public abstract void draw() {
        // 본체 있음
    }
    
    // ✓ 본체 없이 세미콜론
    public abstract void draw();
    
    // 본체 있으면 abstract 가 아님 (일반 구현 메서드)
}

6.7 제약 7 — 자식이 모든 추상 메서드 구현 또는 자기도 추상

public abstract class Shape {
    public abstract double area();
    public abstract double perimeter();
}

// ❌ 에러: area() 구현 안 함
public class Circle extends Shape {
    @Override
    public double perimeter() { return 0; }
}

// ✓ 모두 구현
public class Circle extends Shape {
    @Override
    public double area() { return 0; }
    
    @Override
    public double perimeter() { return 0; }
}

// ✓ 자식도 추상
public abstract class AbstractCircle extends Shape {
    @Override
    public double area() { return 0; }
    // perimeter() 는 다음 자식이 구현
}

6.8 추가 제약들

public abstract class Shape {
    
    // ✓ 인터페이스 구현 가능
    // implements Drawable
    
    // ✓ 다른 추상클래스 상속 가능
    // extends AbstractShape (다른 추상)
    
    // ✓ 일반 클래스 상속 가능 (드물게)
    // extends Object (당연)
    
    // ✓ 추상 메서드 0개 가능
    // (그래도 인스턴스화 불가)
    
    // ✓ 모든 메서드 구현해도 가능
    // (사용자가 인스턴스화 막고 싶을 때)
}

6.9 제약 종합 표

조합가능?이유
abstract class표준
abstract final class모순 (확장 vs 닫음)
abstract + 메서드 본체추상 정의 위반
private abstract 메서드접근 불가 모순
static abstract 메서드polymorphism 무의미
final abstract 메서드override 강제 vs 금지 모순
추상 메서드 in 일반 클래스클래스도 추상이어야
new AbstractClass()인스턴스화 금지
추상 메서드 0개의 abstract class가능
abstract class 의 생성자자식이 호출

6.10 자기 점검 답변

abstract + final, abstract + private, abstract + static 이 모두 에러인 공통 이유는?

:

  • 모두 abstract 의 의도와 모순
  • abstract = "자식이 override 해야 함"
  • final = override 금지 → 모순
  • private = 자식이 접근 불가 → 모순
  • static = polymorphism 무관 → 모순

abstract 는 본질적으로 "확장" 의도, 다른 키워드들과 충돌.


7️⃣ Template Method 패턴

7.1 Template Method 패턴의 정의

Template Method Pattern:

  알고리즘의 골격을 정의하되,
  일부 단계는 자식 클래스가 구현하게 하는 패턴.

GoF 디자인 패턴의 핵심 중 하나.

핵심 아이디어:
  - 추상클래스 = 골격 (template)
  - 자식 클래스 = 채우기 (concrete)

7.2 패턴의 구조

AbstractClass:
  templateMethod() {        ← final, 외부 진입점
    step1();                  ← concrete (공통)
    step2();                  ← abstract (자식 구현)
    step3();                  ← concrete (공통)
    hookMethod();             ← 기본 구현 + 선택적 override
  }
  
  step1() { ... }             ← 공통 구현
  abstract step2();           ← 자식 강제
  step3() { ... }             ← 공통 구현
  hookMethod() { ... }        ← 기본 구현 (선택)

7.3 실제 예 — 음료 만들기

public abstract class Beverage {
    
    // Template Method (final 로 알고리즘 보호)
    public final void prepare() {
        boilWater();
        brew();           // 추상 — 자식 구현
        pourInCup();
        if (customerWantsCondiments()) {   // 훅 메서드
            addCondiments();   // 추상 — 자식 구현
        }
    }
    
    // 공통 구현
    private void boilWater() {
        System.out.println("Boiling water");
    }
    
    private void pourInCup() {
        System.out.println("Pouring into cup");
    }
    
    // 추상 메서드 — 자식이 구현
    protected abstract void brew();
    protected abstract void addCondiments();
    
    // 훅 메서드 — 기본 true, 자식이 override 가능
    protected boolean customerWantsCondiments() {
        return true;
    }
}

public class Tea extends Beverage {
    @Override
    protected void brew() {
        System.out.println("Steeping tea bag");
    }
    
    @Override
    protected void addCondiments() {
        System.out.println("Adding lemon");
    }
}

public class Coffee extends Beverage {
    @Override
    protected void brew() {
        System.out.println("Dripping coffee");
    }
    
    @Override
    protected void addCondiments() {
        System.out.println("Adding sugar and milk");
    }
    
    @Override
    protected boolean customerWantsCondiments() {
        // 사용자 입력으로 결정
        return getUserAnswer();
    }
    
    private boolean getUserAnswer() {
        // ...
        return true;
    }
}

// 사용
Beverage tea = new Tea();
tea.prepare();
// 출력:
// Boiling water
// Steeping tea bag
// Pouring into cup
// Adding lemon

Beverage coffee = new Coffee();
coffee.prepare();
// 출력:
// Boiling water
// Dripping coffee
// Pouring into cup
// Adding sugar and milk

7.4 Template Method 의 효과

1. 알고리즘 골격 보호
   - templateMethod() 는 final
   - 자식이 알고리즘 자체를 못 바꿈
   - 변하는 부분만 자식이 결정

2. 코드 재사용
   - 공통 단계 (boilWater, pourInCup) 는 한 번만
   - 자식마다 반복 안 함

3. 확장 지점 명확
   - abstract = 반드시 구현
   - hook = 선택적 override

4. Open/Closed Principle
   - 알고리즘 닫힘 (변경 X)
   - 단계 열림 (확장 가능)

7.5 ILIC 의 Template Method

// 운송 처리 알고리즘
public abstract class ShipmentProcessor {
    
    // Template Method
    public final void process(Shipment shipment) {
        validate(shipment);
        enrichData(shipment);
        BigDecimal fare = calculateFare(shipment);      // 추상
        applyDiscount(fare, shipment);                  // 훅
        saveResult(shipment, fare);
        notifyCustomer(shipment);                       // 훅
    }
    
    // 공통 구현
    private void validate(Shipment s) {
        Objects.requireNonNull(s);
        if (s.getWeight() <= 0) {
            throw new IllegalArgumentException("Invalid weight");
        }
    }
    
    private void enrichData(Shipment s) {
        // DB 에서 추가 정보 조회
    }
    
    private void saveResult(Shipment s, BigDecimal fare) {
        // DB 저장
    }
    
    // 추상 메서드
    protected abstract BigDecimal calculateFare(Shipment s);
    
    // 훅 메서드 (기본 동작 + override 가능)
    protected BigDecimal applyDiscount(BigDecimal fare, Shipment s) {
        return fare;   // 기본: 할인 없음
    }
    
    protected void notifyCustomer(Shipment s) {
        // 기본: 이메일
        emailService.send(s.getCustomerEmail());
    }
}

// 해상 운송
public class SeaShipmentProcessor extends ShipmentProcessor {
    
    @Override
    protected BigDecimal calculateFare(Shipment s) {
        return s.getWeight() * 0.5;
    }
    
    @Override
    protected BigDecimal applyDiscount(BigDecimal fare, Shipment s) {
        // 대량 운송 할인
        if (s.getWeight() > 1000) {
            return fare.multiply(new BigDecimal("0.9"));
        }
        return fare;
    }
}

// 항공 운송
public class AirShipmentProcessor extends ShipmentProcessor {
    
    @Override
    protected BigDecimal calculateFare(Shipment s) {
        return s.getWeight() * 3.0;
    }
    
    @Override
    protected void notifyCustomer(Shipment s) {
        // 항공은 SMS + 이메일
        emailService.send(s.getCustomerEmail());
        smsService.send(s.getCustomerPhone());
    }
}

7.6 Template Method 의 변형

1. Strategy 패턴과 비교

Template Method:
  - 상속 기반
  - 알고리즘 골격은 부모
  - 변하는 부분은 자식

Strategy:
  - 컴포지션 기반
  - 알고리즘 전체를 외부 객체로
  - 런타임에 교체 가능

2. Factory Method 패턴

public abstract class Document {
    
    // Template Method
    public final void open() {
        loadFile();
        Parser parser = createParser();   // ← Factory Method
        parser.parse();
        render();
    }
    
    // Factory Method
    protected abstract Parser createParser();
}

public class PdfDocument extends Document {
    @Override
    protected Parser createParser() {
        return new PdfParser();
    }
}

Factory Method 도 Template Method 의 일종.

7.7 Template Method 의 단점

1. 깊은 상속 계층
   - 자식 → 손자 → 증손자 ...
   - 유지보수 어려움

2. final 의 강제
   - 알고리즘 자체는 변경 불가
   - 비슷하지만 약간 다른 흐름은 못 만듦

3. 자식 클래스 폭발
   - 변형마다 새 자식
   - Strategy 가 더 유연

4. 부모-자식 강결합
   - 자식이 부모 내부 의존
   - 부모 변경 시 자식 영향

7.8 자기 점검 답변

Template Method 패턴의 핵심 메커니즘과 추상클래스와의 관계는?

:

  • Template Method:

    • 알고리즘 골격을 부모에서 정의
    • 변하는 단계는 자식이 구현
    • 부모 메서드는 final (보호)
    • 자식 메서드는 abstract 또는 hook
  • 추상클래스와의 관계:

    • 추상클래스의 가장 흔한 활용 패턴
    • 공통 코드 (구현 메서드) + 확장 지점 (추상 메서드)
    • 추상클래스 없이 Template Method 어려움
    • 자바 라이브러리의 핵심 (HttpServlet, AbstractList 등)

추상클래스의 진정한 가치는 Template Method.


8️⃣ 자바 표준 라이브러리의 활용

8.1 AbstractList — List 의 토대

public abstract class AbstractList<E> extends AbstractCollection<E> 
                                       implements List<E> {
    
    // 추상 메서드 — 자식이 반드시 구현
    public abstract E get(int index);
    
    public abstract int size();
    
    // 구현 메서드 — 자식 메서드 활용
    public boolean add(E e) {
        add(size(), e);   // 다른 메서드 호출
        return true;
    }
    
    public Iterator<E> iterator() {
        return new Itr();   // 내부 Iterator 클래스 제공
    }
    
    public int indexOf(Object o) {
        ListIterator<E> it = listIterator();
        while (it.hasNext()) {
            if (Objects.equals(o, it.next())) {
                return it.previousIndex();
            }
        }
        return -1;
    }
    
    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }
    
    public boolean equals(Object o) {
        // List 의 표준 equals 구현
        if (o == this) return true;
        if (!(o instanceof List)) return false;
        // ...
    }
    
    public int hashCode() {
        // List 의 표준 hashCode 구현
        int hashCode = 1;
        for (E e : this) {
            hashCode = 31 * hashCode + (e == null ? 0 : e.hashCode());
        }
        return hashCode;
    }
    
    // ... 더 많은 메서드
}

핵심:

  • get(int) + size() 만 구현하면 List 완성
  • 나머지 메서드 (contains, iterator, equals, hashCode) 자동 제공
  • ArrayList 도 AbstractList 상속

8.2 ArrayList 가 AbstractList 활용하는 방법

public class ArrayList<E> extends AbstractList<E>
                          implements List<E>, ... {
    
    transient Object[] elementData;
    private int size;
    
    @Override
    public E get(int index) {
        Objects.checkIndex(index, size);
        return (E) elementData[index];
    }
    
    @Override
    public int size() {
        return size;
    }
    
    // ArrayList 만의 최적화
    @Override
    public boolean add(E e) {
        // 배열 직접 조작 (AbstractList 의 기본보다 빠름)
        elementData[size++] = e;
        return true;
    }
    
    // ...
}

→ AbstractList 가 90% 구현, ArrayList 는 효율 최적화.

8.3 AbstractMap — Map 의 토대

public abstract class AbstractMap<K, V> implements Map<K, V> {
    
    // 추상 메서드 — 자식 강제 구현
    public abstract Set<Map.Entry<K, V>> entrySet();
    
    // 구현 메서드 — entrySet 활용
    public int size() {
        return entrySet().size();
    }
    
    public boolean isEmpty() {
        return size() == 0;
    }
    
    public V get(Object key) {
        for (Entry<K, V> entry : entrySet()) {
            if (Objects.equals(entry.getKey(), key)) {
                return entry.getValue();
            }
        }
        return null;
    }
    
    public boolean containsKey(Object key) {
        return get(key) != null;
    }
    
    // ... 더 많은 메서드
}

핵심:

  • entrySet() 만 구현하면 Map 완성
  • HashMap 등 더 효율적 구현은 오버라이드

8.4 HttpServlet — 웹 프레임워크의 토대

public abstract class HttpServlet extends GenericServlet {
    
    // Template Method
    protected void service(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        
        String method = req.getMethod();
        
        if ("GET".equals(method)) {
            doGet(req, resp);
        } else if ("POST".equals(method)) {
            doPost(req, resp);
        } else if ("PUT".equals(method)) {
            doPut(req, resp);
        } else if ("DELETE".equals(method)) {
            doDelete(req, resp);
        }
        // ...
    }
    
    // 훅 메서드들 — 자식이 필요한 것만 override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        // 기본: 405 Method Not Allowed
        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
    }
    
    protected void doPost(...) { ... }   // 기본 405
    protected void doPut(...) { ... }    // 기본 405
    protected void doDelete(...) { ... } // 기본 405
}

// 사용자가 구현
public class MyServlet extends HttpServlet {
    
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws IOException {
        resp.getWriter().println("Hello");
    }
    
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
            throws IOException {
        // POST 처리
    }
    
    // doPut, doDelete 안 만들어도 됨 (기본 405)
}

8.5 InputStream / OutputStream

public abstract class InputStream implements Closeable {
    
    // 추상 메서드 — 자식 강제
    public abstract int read() throws IOException;
    
    // 구현 메서드 — read() 활용
    public int read(byte[] b, int off, int len) throws IOException {
        // 기본 구현: read() 반복 호출
        for (int i = 0; i < len; i++) {
            int c = read();
            if (c == -1) return i;
            b[off + i] = (byte) c;
        }
        return len;
    }
    
    public long skip(long n) throws IOException {
        // ...
    }
    
    public int available() throws IOException {
        return 0;
    }
}

// 자식 클래스
public class FileInputStream extends InputStream {
    
    @Override
    public int read() throws IOException {
        // 파일에서 한 바이트 읽기
        return readNative();
    }
    
    // 더 효율적인 read(byte[], int, int) 구현
    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        // native 호출로 빠른 읽기
        return readBytes(b, off, len);
    }
}

8.6 자바 표준 라이브러리 추상클래스 종합

컬렉션:
  - AbstractCollection
  - AbstractList
  - AbstractSet
  - AbstractMap
  - AbstractQueue
  - AbstractSequentialList

I/O:
  - InputStream / OutputStream
  - Reader / Writer
  - FilterInputStream / FilterOutputStream

웹:
  - HttpServlet
  - GenericServlet
  - AbstractHandler (Spring)

기타:
  - Number (Integer, Long 등의 부모)
  - Calendar (GregorianCalendar 의 부모)
  - ClassLoader
  - Charset

8.7 Spring 의 추상클래스 활용

// Spring MVC 의 핵심
public abstract class AbstractController extends WebContentGenerator 
                                          implements Controller {
    
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, 
                                       HttpServletResponse response) 
            throws Exception {
        
        // Template Method
        checkRequest(request);
        prepareResponse(response);
        return handleRequestInternal(request, response);
    }
    
    // 자식이 구현
    protected abstract ModelAndView handleRequestInternal(
        HttpServletRequest request, HttpServletResponse response) 
        throws Exception;
}

// JPA Repository
public abstract class SimpleJpaRepository<T, ID> 
        implements JpaRepositoryImplementation<T, ID> {
    
    @Override
    public Optional<T> findById(ID id) {
        // 공통 구현
    }
    
    @Override
    public List<T> findAll() {
        // 공통 구현
    }
    
    // ... 모든 표준 메서드 구현
}

8.8 자기 점검 답변

AbstractList 가 어떻게 List 인터페이스의 90% 를 처리하나?

:
1. 추상 메서드 강제: get(int) + size()
2. 다른 메서드는 이 둘로 구현:

  • containsindexOf 활용
  • iterator → 내부 Itr 클래스
  • equals, hashCode → 표준 구현
  • add, remove → 인덱스 활용
  1. 결과:
    • ArrayList 는 2개 메서드 + 최적화만
    • LinkedList 도 비슷
    • 새로운 List 구현 시 빠르게 가능

추상클래스가 표준 라이브러리의 코드 재사용 핵심.


9️⃣ 면접 + 자기 점검

9.1 면접 단골 질문 매핑

Q핵심 답변
abstract 키워드의 의미?불완전, 직접 인스턴스화 불가
추상클래스 vs 일반 클래스?인스턴스화 가부 + 추상 메서드 가능 여부
추상 메서드의 4가지 제약?본체 X, private X, final X, static X
추상클래스의 생성자?자식이 super() 로 호출
추상클래스에 생성자 있는 이유?부모 필드 초기화
단일 상속 채택 이유?Diamond Problem 회피
abstract + final 에러?확장 vs 닫음의 모순
Template Method 패턴?골격은 부모, 단계는 자식
AbstractList 의 역할?get + size 만 구현하면 List 완성
HttpServlet 의 service 메서드?doGet/doPost 등 디스패치
@Override 의 효과?컴파일러 검증, 오타 발견
공변 반환 타입?자식이 더 좁은 타입 반환 가능

9.2 자기 점검 체크리스트

기본 이해

  • abstract 의 두 가지 사용 (클래스/메서드) 을 안다
  • 추상클래스의 5가지 멤버를 안다
  • 추상 메서드의 4가지 제약을 안다
  • 직접 인스턴스화 불가의 이유를 안다
  • 익명 자식 클래스 패턴을 안다

생성자

  • 추상클래스 생성자의 호출 흐름을 안다
  • super() 의 명시/암묵 호출을 안다
  • 생성자에서 추상 메서드 호출의 위험을 안다
  • 생성자 접근 제어자 권장 (protected) 를 안다

단일 상속

  • Diamond Problem 을 설명할 수 있다
  • 단일 상속의 우회 방법 3가지를 안다
  • 인터페이스 다중 구현의 의미를 안다
  • Composition vs Inheritance 를 안다

제약과 에러

  • abstract + final 에러를 안다
  • abstract + private/static 에러를 안다
  • abstract 메서드 in 일반 클래스 에러를 안다
  • 자식이 모든 추상 메서드 구현 강제를 안다

Template Method 패턴

  • Template Method 의 구조를 안다
  • hook 메서드의 역할을 안다
  • ILIC 적용 가능
  • Factory Method 와의 관계를 안다

자바 라이브러리

  • AbstractList 의 구조를 안다
  • AbstractMap 의 entrySet 추상화를 안다
  • HttpServlet 의 doGet/doPost 패턴을 안다
  • Spring 의 활용을 안다

9.3 추가 심화 질문

Q1: 추상클래스를 final 처럼 만들 수 있나?

답:

  • 직접적인 방법: 모든 생성자를 private
  • 결과: 외부에서 상속 불가
  • 하지만 추상클래스의 의도와 모순
  • 다른 방법: 일반 클래스 + private 생성자 + static factory

Q2: 추상클래스가 다른 추상클래스를 상속할 수 있나?

답:

  • 가능
  • 추상 메서드 누적 가능
  • 손자가 모두 구현하거나 자기도 추상
abstract class A {
    abstract void m1();
}

abstract class B extends A {
    abstract void m2();
    // m1() 도 여전히 추상
}

class C extends B {
    @Override void m1() { ... }
    @Override void m2() { ... }
}

Q3: 추상 메서드를 protected 로 하는 이유는?

답:

  • 외부에 노출 안 함
  • 자식만 구현 + 부모가 호출 (Template Method)
  • 공개 API 와 확장 지점 분리
  • 캡슐화 강화

Q4: 추상클래스에 main 메서드를 둘 수 있나?

답:

  • 가능
  • static 메서드는 인스턴스 무관
  • 추상클래스도 클래스이므로 main 가능
  • 단, 추상클래스를 직접 인스턴스화 못 함
public abstract class AbstractRunner {
    public abstract void run();
    
    public static void main(String[] args) {
        // 자식 클래스 사용
        AbstractRunner runner = new ConcreteRunner();
        runner.run();
    }
}

🎯 핵심 요약 — 3줄 정리

1. abstract = "불완전함"

  • 클래스: 인스턴스화 불가
  • 메서드: 본체 없음, 자식 구현 강제
  • 4가지 제약: private X, final X, static X, 본체 X

2. 추상클래스의 가치

  • 공통 코드 + 확장 지점
  • Template Method 패턴의 토대
  • 자식 클래스 강제 (안전한 확장)
  • 부분 캡슐화 (protected)

3. 자바 라이브러리의 핵심

  • AbstractList, AbstractMap (컬렉션)
  • InputStream, OutputStream (I/O)
  • HttpServlet (웹)
  • Spring 의 AbstractController, SimpleJpaRepository

📚 다음으로...

Unit 4.2 — 인터페이스의 특징

이번 Unit에서 추상클래스를 봤다면, 다음은 인터페이스의 본질.

  • interface 키워드의 의미
  • 다중 상속 (구현) 가능
  • public static final 자동
  • public abstract 자동 (Java 7 까지)
  • 생성자 없음
  • 인스턴스 변수 없음 (상수만)
  • 인터페이스의 다양한 활용

Phase 4 진행 상황

🚀 Phase 4 — 추상화의 두 도구
  ✅ Unit 4.1 추상클래스의 특징 ← 여기
  ⏭ Unit 4.2 인터페이스의 특징
  ⏭ Unit 4.3 Java 8 default & static 메서드
  ⏭ Unit 4.4 추상클래스 vs 인터페이스 선택 기준

3주차 누적 진행

✅ Phase 1 — Pass by Value (1.1 ~ 1.3 완주)
✅ Phase 2 — 컬렉션 프레임워크 (2.1 ~ 2.6 완주)
✅ Phase 3 — 해시의 원리 (3.1 ~ 3.4 완주)
🚀 Phase 4 — 추상화의 두 도구 (1/4 진행)

총: 14/43 Unit 작성 (약 33%)
profile
Software Developer

0개의 댓글