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

Psj·2026년 5월 19일

F-lab

목록 보기
88/240

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. 다른 메서드는 이 둘로 구현:

  • contains → indexOf 활용
  • 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개의 댓글