3주차 Unit 4.3 — Java 8 default & static 메서드

Psj·2026년 5월 19일

F-lab

목록 보기
90/197

Unit 4.3 — Java 8 default & static 메서드

F-LAB JAVA · 3주차 · Phase 4 · 추상화의 두 도구


📌 학습 목표

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

  • default 메서드 의 정확한 정의와 등장 배경은?
  • 하위 호환성 (Backward Compatibility) 의 문제와 default 의 해결은?
  • default 메서드의 4가지 규칙 (오버라이드, 다이아몬드, 클래스 우선 등) 은?
  • Diamond Problem 의 부활 과 컴파일러의 해결 방법은?
  • 인터페이스의 static 메서드 와 클래스의 static 메서드 차이는?
  • private 메서드 (Java 9+) 의 역할은?
  • Stream/Optional/Collection 에서 default 활용 사례는?
  • Comparator 의 default 메서드 (reversed, thenComparing) 의 의미는?
  • default 와 추상클래스의 구현 메서드 의 차이는?
  • default 메서드의 3가지 단점 과 사용 시 주의는?

🎯 핵심 한 문장

Java 8 의 default 메서드는 "인터페이스에 구현을 추가" 할 수 있게 한 혁명이다.
기존 인터페이스를 깨지 않고 새 메서드를 추가할 수 있게 되어,
Collection.stream(), Comparator.reversed() 같은 풍부한 API 가 가능해졌다.
다중 상속이 가능해지면서 Diamond Problem 이 부활했지만, 명시적 충돌 해결 규칙 으로 안전하게 처리한다.
Java 9+ 의 private 메서드까지 더해져 인터페이스는 추상클래스와 거의 동등한 표현력을 갖게 되었다.

비유 — 표준 매뉴얼의 진화

Java 1.0 ~ 7 (순수 명세):
  매뉴얼 = 해야 할 일 목록만
  "draw() 를 구현하라"
  → 구현 방법은 각자 알아서

Java 8 (default 추가):
  매뉴얼 = 해야 할 일 목록 + 기본 방법 가이드
  "draw() 를 구현하라"
  "기본 방법: 이렇게 하면 됨 (default)"
  → 원하면 따르고, 원하면 자신만의 방법

→ default = 기본 구현 제공 + 선택적 오버라이드.


🧭 9개 섹션 로드맵

1. default 메서드의 등장 배경
2. default 메서드의 문법과 의미
3. Diamond Problem 의 부활과 해결
4. 인터페이스의 static 메서드
5. private 메서드 (Java 9+)
6. 자바 표준 라이브러리의 default 활용
7. default vs 추상클래스 구현 메서드
8. default 메서드의 단점과 주의
9. 면접 + 자기 점검

1️⃣ default 메서드의 등장 배경

1.1 Java 7 이전의 문제

Java 1.2 (1998): Collection 인터페이스
  public interface Collection<E> {
      boolean add(E e);
      boolean remove(Object o);
      // ... 기존 메서드들
  }

Java 8 (2014) 에서 추가하고 싶었던 메서드들:
  - forEach(Consumer)    — 람다 순회
  - removeIf(Predicate)  — 조건 삭제
  - stream()             — Stream API 진입점
  - parallelStream()
  - spliterator()

문제:
  Collection 인터페이스에 새 메서드 추가하면?
  → 모든 구현체가 컴파일 에러
  → ArrayList, HashSet, LinkedList, ... 모두 깨짐
  → 사용자 코드의 Collection 구현체 깨짐
  → 거대한 호환성 문제

1.2 호환성의 두 종류

1. 소스 호환성 (Source Compatibility):
   - 기존 소스 코드 재컴파일 가능
   - 컴파일 에러 없음

2. 바이너리 호환성 (Binary Compatibility):
   - 기존 .class 파일이 새 JVM 에서 실행
   - 링크 에러 없음

Java 8 의 도전:
  Collection 에 stream() 메서드 추가하면서
  둘 다 깨지 않기.

1.3 해결책 1: 새 인터페이스

// 가능했던 방법 1:
public interface Collection<E> { /* 기존 */ }

public interface CollectionV2<E> extends Collection<E> {
    void forEach(Consumer<? super E> action);
    Stream<E> stream();
    // ...
}

// 단점:
// - 사용자가 어느 것을 쓸지 혼란
// - 기존 코드가 새 메서드 못 씀
// - 라이브러리 분열

1.4 해결책 2: 추상클래스 강요

// 가능했던 방법 2:
public abstract class AbstractCollection<E> {
    abstract Iterator<E> iterator();
    abstract int size();
    
    // 새 메서드 구현
    public void forEach(Consumer<? super E> action) { ... }
}

// 단점:
// - Collection 인터페이스의 본질 (다중 구현) 손상
// - 기존 코드 수정 필요
// - 다른 인터페이스 (예: Map) 도 같은 문제

1.5 해결책 3 (선택) — default 메서드

// Java 8+ 의 해결:
public interface Collection<E> {
    boolean add(E e);
    boolean remove(Object o);
    // ... 기존 추상 메서드
    
    // ★ 새로 추가: default 메서드
    default void forEach(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        for (E t : this) {
            action.accept(t);
        }
    }
    
    default boolean removeIf(Predicate<? super E> filter) {
        // 기본 구현
    }
    
    default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }
}

// 효과:
// - 기존 구현체 (ArrayList 등) 그대로 사용 가능
// - 새 메서드도 자동 사용 가능 (default 구현으로)
// - 필요시 오버라이드로 효율 최적화
// - 사용자 코드 깨짐 없음

1.6 default 메서드의 효과

이전 (Java 7):
  List<String> list = new ArrayList<>();
  
  // forEach 없음 → 직접 for-each
  for (String s : list) {
      System.out.println(s);
  }
  
  // stream() 없음 → 외부 라이브러리 (Guava 등)

Java 8+:
  List<String> list = new ArrayList<>();
  
  // forEach 사용 가능
  list.forEach(System.out::println);
  
  // stream() 사용 가능
  list.stream()
      .filter(s -> s.length() > 5)
      .forEach(System.out::println);

1.7 default 메서드의 의의

1. 인터페이스의 진화 가능
   - 기존 인터페이스에 새 메서드 추가
   - 호환성 유지
   - API 풍부화

2. 함수형 프로그래밍 진입
   - Stream API 도입의 토대
   - 람다와 자연스러운 결합

3. 인터페이스의 표현력 ↑
   - 단순 명세 → 부분 구현 포함
   - 추상클래스에 가까워짐

4. 코드 중복 ↓
   - 공통 구현을 default 로
   - 모든 구현체가 자동 보유

1.8 자기 점검 답변

default 메서드가 Java 8 에 추가된 이유는?

:
1. 하위 호환성 유지:

  • 기존 인터페이스 (Collection 등) 에 새 메서드 추가 필요
  • 추가 시 모든 구현체 깨짐
  • 기본 구현 제공으로 해결
  1. Stream API 등 함수형 기능 도입:

    • Collection.stream() 등 필요
    • 모든 컬렉션에 자동 제공
  2. API 풍부화:

    • Comparator.reversed(), thenComparing() 등
    • 인터페이스의 표현력 ↑

하위 호환성 + 진화 가능성 의 균형.


2️⃣ default 메서드의 문법과 의미

2.1 default 메서드 문법

public interface Greeter {
    
    // 추상 메서드
    String getName();
    
    // default 메서드 — 본체 있음
    default String greet() {
        return "Hello, " + getName();
    }
    
    // default 메서드는 다른 추상/default 메서드 호출 가능
    default String formal() {
        return "Dear " + greet();
    }
}

// 구현
public class Person implements Greeter {
    private String name;
    
    @Override
    public String getName() {
        return name;
    }
    
    // greet, formal 구현 안 해도 됨 — default 사용
}

// 사용
Person p = new Person("Alice");
p.greet();    // "Hello, Alice"
p.formal();   // "Dear Hello, Alice"

2.2 default 메서드의 4가지 규칙

1. default 키워드 명시
   - default void method() { ... }
   - public default 도 가능 (public 자동)

2. 본체 작성 필수
   - { } 또는 코드
   - 추상 메서드와 다름

3. 자동으로 public
   - public default void m() — public 자동
   - private default 불가

4. final 가능 X
   - default 와 final 조합 불가
   - 구현체가 오버라이드 가능

2.3 default 메서드의 오버라이드

public interface Greeter {
    default String greet() {
        return "Hello";
    }
}

// 1. 그대로 사용
public class Person implements Greeter {
    // greet() 그대로
}

// 2. 오버라이드
public class FormalPerson implements Greeter {
    @Override
    public String greet() {
        return "Good day";
    }
}

// 3. 추상으로 다시 만들기
public abstract class AbstractPerson implements Greeter {
    @Override
    public abstract String greet();   // 다시 추상으로
}

// 4. super 호출
public class CombinedPerson implements Greeter {
    @Override
    public String greet() {
        return Greeter.super.greet() + ", everyone";   // ★ Greeter.super
    }
}

Greeter.super.greet():

  • 인터페이스의 default 메서드 명시적 호출
  • 클래스의 super 와 비슷한 의미

2.4 인터페이스 default 메서드 호출 흐름

public interface Animal {
    default String sound() {
        return "Some sound";
    }
}

public class Dog implements Animal {
    // sound() 그대로
}

Dog d = new Dog();
d.sound();   // "Some sound"

// 메서드 해결 흐름:
// 1. Dog 의 메서드 검색 → 없음
// 2. Dog 의 부모 클래스 검색 → Object 의 sound() 없음
// 3. Dog 가 구현한 인터페이스 검색
//    → Animal 의 default sound() 발견
// 4. Animal.sound() 실행

2.5 default 메서드의 활용 패턴

패턴 1: 편의 메서드 (Convenience Methods)

public interface Calculator {
    int add(int a, int b);
    
    // 편의 default 메서드
    default int addThree(int a, int b, int c) {
        return add(add(a, b), c);
    }
    
    default int doubleAdd(int a, int b) {
        return add(a, b) * 2;
    }
}

패턴 2: 기본 구현

public interface Loggable {
    String getName();
    
    // 기본 구현 (대부분 만족)
    default void log(String message) {
        System.out.println("[" + getName() + "] " + message);
    }
    
    default void logError(String message) {
        log("ERROR: " + message);
    }
}

패턴 3: 메서드 조합

public interface Predicate<T> {
    boolean test(T t);
    
    // 조합 default 메서드
    default Predicate<T> and(Predicate<? super T> other) {
        return t -> test(t) && other.test(t);
    }
    
    default Predicate<T> or(Predicate<? super T> other) {
        return t -> test(t) || other.test(t);
    }
    
    default Predicate<T> negate() {
        return t -> !test(t);
    }
}

// 사용
Predicate<String> notEmpty = s -> !s.isEmpty();
Predicate<String> notTooLong = s -> s.length() < 100;

Predicate<String> valid = notEmpty.and(notTooLong);

2.6 default 메서드와 다중 구현

public interface Swimmer {
    default void move() {
        System.out.println("Swimming");
    }
}

public interface Walker {
    default void move() {
        System.out.println("Walking");
    }
}

// ❌ 충돌
public class Duck implements Swimmer, Walker {
    // 컴파일 에러:
    // class Duck inherits unrelated defaults for move() 
    // from types Swimmer and Walker
}

// ✓ 해결: 명시적 오버라이드
public class Duck implements Swimmer, Walker {
    @Override
    public void move() {
        Swimmer.super.move();   // Swimmer 의 default
        Walker.super.move();    // Walker 의 default
    }
}

Diamond Problem 의 부활 (다음 섹션).

2.7 자기 점검 답변

default 메서드를 추가할 때 주의할 점은?

:
1. 이름 충돌 가능성: 같은 시그니처가 여러 인터페이스에 있으면 충돌
2. 구현 가정: 모든 구현체가 default 로 만족하지 않을 수 있음
3. 테스트 부담: default 코드도 모든 구현체에서 동작 확인
4. 상태 의존 회피: default 는 인스턴스 필드 접근 X
5. 단순한 구현 권장: 복잡한 default 는 추상클래스 고려


3️⃣ Diamond Problem 의 부활과 해결

3.1 클래스 vs 인터페이스의 Diamond

클래스 다중 상속 (자바 X):
  
       A.m()
      /     \
     B       C
      \     /
        D
  
  D 가 A 의 m() 을 어떻게 받나?
  → Diamond Problem
  
자바는 단일 상속으로 회피.

인터페이스 (Java 7 까지):
  
       A
      / \
     B   C
      \ /
       D
  
  모든 메서드가 abstract → 구현 X
  → 충돌 없음

인터페이스 (Java 8+):
  default 메서드 등장
  → 구현 있음
  → Diamond Problem 부활!

3.2 Diamond Problem 의 시나리오

public interface A {
    default void m() {
        System.out.println("A");
    }
}

public interface B extends A {
    @Override
    default void m() {
        System.out.println("B");
    }
}

public interface C extends A {
    @Override
    default void m() {
        System.out.println("C");
    }
}

public class D implements B, C {
    // D.m() 은 B 의 m()? C 의 m()? A 의 m()?
    // 모호함 → 컴파일 에러
}

3.3 자바의 충돌 해결 규칙

규칙 1: 클래스 우선 (Class Wins)
  슈퍼클래스의 메서드 > 인터페이스의 default

규칙 2: 더 구체적인 인터페이스 우선 (More Specific Interface Wins)
  B extends A 라면 B 의 default > A 의 default

규칙 3: 명시적 충돌 해결 (Explicit Override)
  여러 default 가 충돌 시 컴파일 에러
  → 구현 클래스가 명시적으로 오버라이드해야

3.4 규칙 1 — 클래스 우선

public interface Greeter {
    default String greet() {
        return "Hi from interface";
    }
}

public class BaseClass {
    public String greet() {
        return "Hi from class";
    }
}

public class Person extends BaseClass implements Greeter {
    // Person.greet() 은?
}

Person p = new Person();
p.greet();   // "Hi from class" — 클래스 우선!

이유:

  • 클래스의 메서드가 더 구체적
  • 명시적 의도
  • 인터페이스 default 는 "기본값"

3.5 규칙 2 — 더 구체적 인터페이스 우선

public interface A {
    default void m() {
        System.out.println("A");
    }
}

public interface B extends A {
    @Override
    default void m() {
        System.out.println("B");
    }
}

public class D implements B {
    // D.m() 은?
}

D d = new D();
d.m();   // "B" — 더 구체적인 B 우선

이유:

  • B 가 A 의 자식 (extends A)
  • B 가 더 구체적
  • 자연스러운 상속 흐름

3.6 규칙 3 — 명시적 충돌 해결

public interface Swimmer {
    default void move() {
        System.out.println("Swim");
    }
}

public interface Walker {
    default void move() {
        System.out.println("Walk");
    }
}

// 두 인터페이스 모두 default move()
// 상속 관계 없음 → 충돌

public class Duck implements Swimmer, Walker {
    // ❌ 컴파일 에러
    // class Duck inherits unrelated defaults for move()
}

// 해결 1: 명시적 오버라이드 + super 호출
public class Duck implements Swimmer, Walker {
    @Override
    public void move() {
        Swimmer.super.move();   // 또는 Walker.super.move()
    }
}

// 해결 2: 새 구현
public class Duck implements Swimmer, Walker {
    @Override
    public void move() {
        System.out.println("Duck moves uniquely");
    }
}

// 해결 3: 둘 다 호출
public class Duck implements Swimmer, Walker {
    @Override
    public void move() {
        Swimmer.super.move();
        Walker.super.move();
    }
}

3.7 복잡한 다이아몬드 시나리오

public interface A {
    default void m() { System.out.println("A"); }
}

public interface B extends A {
    @Override
    default void m() { System.out.println("B"); }
}

public interface C extends A {
    @Override
    default void m() { System.out.println("C"); }
}

public class D implements B, C {
    // B 와 C 가 모두 A 를 상속
    // 둘 다 m() 오버라이드
    // 충돌!
}

// 해결
public class D implements B, C {
    @Override
    public void m() {
        B.super.m();   // 또는 C.super.m()
    }
}

3.8 규칙 정리

충돌 결정 우선순위:
  1. 슈퍼클래스의 메서드 (있다면)
  2. 더 구체적 인터페이스의 default (extends 관계)
  3. 위가 모두 모호하면 → 명시적 오버라이드 필수

3.9 자기 점검 답변

Diamond Problem 이 default 메서드로 부활하면서 자바가 어떻게 해결했나?

:

  • 부활 원인:

    • Java 7 까지: 인터페이스에 구현 X → 충돌 없음
    • Java 8+: default 메서드 등장 → 구현 충돌 가능
  • 해결 규칙 3가지:

    1. 클래스 우선: 슈퍼클래스 > 인터페이스
    2. 구체적 인터페이스 우선: B extends A 면 B
    3. 명시적 충돌 해결: 같은 레벨 두 default 면 오버라이드 강제
  • 결과:

    • 컴파일 타임 모호함 식별
    • 명시적 해결로 안전
    • C++의 비명시적 다중 상속과 차별화

4️⃣ 인터페이스의 static 메서드

4.1 인터페이스 static 메서드의 정의

public interface Calculator {
    
    // 추상 메서드
    int add(int a, int b);
    
    // ★ Java 8+: static 메서드
    static Calculator createDefault() {
        return (a, b) -> a + b;
    }
    
    static Calculator multiply() {
        return (a, b) -> a * b;
    }
}

// 사용
Calculator c1 = Calculator.createDefault();   // 인터페이스명.메서드
c1.add(2, 3);   // 5

Calculator c2 = Calculator.multiply();
c2.add(2, 3);   // 6 (이름은 add 지만 곱셈)

4.2 static 메서드의 4가지 특성

1. 인터페이스명으로 호출
   Calculator.createDefault()
   
2. 구현 클래스로는 호출 X
   public class MyCalc implements Calculator { ... }
   MyCalc.createDefault();   // ❌ 컴파일 에러
   
3. 인스턴스로도 호출 X
   Calculator c = new ...;
   c.createDefault();   // ❌ 컴파일 에러
   
4. 자동으로 public
   static Calculator createDefault() — public 자동

4.3 인터페이스 static 메서드 vs 클래스 static 메서드

인터페이스 static:
  - 인터페이스명으로만 호출
  - 구현체에 상속 안 됨
  - 인터페이스 전용 유틸리티

클래스 static:
  - 클래스명 또는 인스턴스로 호출 (인스턴스로는 권장 X)
  - 자식 클래스에 상속 (overriding 은 hiding)
  - 일반 유틸리티

4.4 등장 배경

Java 7 까지:
  인터페이스에 static 메서드 X
  
  관련 유틸리티는 별도 클래스로:
    Collection 인터페이스
    Collections 유틸리티 클래스 (별도)
  
  예: Collections.sort(list)
  예: Collections.unmodifiableList(list)

Java 8+:
  인터페이스에 직접 static 메서드 추가 가능
  
  유틸리티가 인터페이스 안에:
    Stream.of(1, 2, 3)
    List.of("a", "b")
    Map.of("k", "v")
  
  → 사용자가 별도 클래스 찾을 필요 X

4.5 자바 표준 라이브러리의 활용

// Comparator 의 static 메서드
Comparator.naturalOrder();
Comparator.reverseOrder();
Comparator.comparing(Shipment::getId);
Comparator.comparingInt(Shipment::getWeight);

// Stream 의 static 메서드
Stream.of(1, 2, 3);
Stream.empty();
Stream.generate(() -> 0);
Stream.iterate(0, n -> n + 1);

// List/Map/Set 의 of (Java 9+)
List.of("a", "b", "c");
Map.of("k1", "v1", "k2", "v2");
Set.of(1, 2, 3);

// Optional 의 static
Optional.of(value);
Optional.empty();
Optional.ofNullable(value);

// Path/Paths 의 of
Path.of("dir", "file.txt");

// Predicate 의 static
Predicate.isEqual(target);
Predicate.not(predicate);   // Java 11+

4.6 ILIC 적용

public interface ShipmentValidator {
    boolean validate(Shipment shipment);
    
    // static 팩토리 메서드들
    static ShipmentValidator alwaysTrue() {
        return s -> true;
    }
    
    static ShipmentValidator weight(int min, int max) {
        return s -> s.getWeight() >= min && s.getWeight() <= max;
    }
    
    static ShipmentValidator port(Set<String> allowedPorts) {
        return s -> allowedPorts.contains(s.getOriginPort());
    }
    
    // default 조합 메서드
    default ShipmentValidator and(ShipmentValidator other) {
        return s -> validate(s) && other.validate(s);
    }
}

// 사용
ShipmentValidator validator = 
    ShipmentValidator.weight(0, 10000)
        .and(ShipmentValidator.port(Set.of("BUSAN", "INCHEON")));

Shipment s = ...;
boolean valid = validator.validate(s);

static + default 의 강력한 조합.

4.7 자기 점검 답변

인터페이스의 static 메서드가 등장한 이유는?

:
1. 유틸리티 클래스 분리 해소:

  • Java 7 까지: Collection + Collections (분리)
  • Java 8+: 인터페이스 안에 직접
  • 코드 응집도 ↑
  1. 팩토리 메서드 제공:

    • List.of, Map.of, Set.of
    • Stream.of, Optional.of
    • 사용 진입점 명확
  2. 관련 유틸리티 통합:

    • Comparator.naturalOrder() 등
    • 관련 동작이 한 곳에
  3. 함수형 인터페이스와 결합:

    • Predicate.isEqual, Function.identity 등
    • 람다와 자연스러운 조합

5️⃣ private 메서드 (Java 9+)

5.1 private 메서드의 등장

Java 9+ 추가:
  - private 메서드
  - private static 메서드

목적:
  - default/static 메서드 간 코드 공유
  - 외부 노출 없는 내부 구현

5.2 등장 배경 — 코드 중복 문제

// Java 8 의 문제 — default 메서드 간 중복
public interface Calculator {
    
    default int sumPositives(List<Integer> nums) {
        int total = 0;
        for (int n : nums) {
            if (n > 0) {   // 중복 1
                total += n;
            }
        }
        return total;
    }
    
    default int countPositives(List<Integer> nums) {
        int count = 0;
        for (int n : nums) {
            if (n > 0) {   // 중복 2
                count++;
            }
        }
        return count;
    }
    
    // n > 0 검사 로직이 중복
    // public default 메서드로 만들면 외부 노출
    // 내부 헬퍼 메서드 필요 → Java 9+ private 도입
}

5.3 private 메서드로 해결

public interface Calculator {
    
    default int sumPositives(List<Integer> nums) {
        return nums.stream()
            .filter(this::isPositive)   // private 호출
            .mapToInt(Integer::intValue)
            .sum();
    }
    
    default int countPositives(List<Integer> nums) {
        return (int) nums.stream()
            .filter(this::isPositive)   // private 호출
            .count();
    }
    
    // ★ Java 9+: private 메서드
    private boolean isPositive(int n) {
        return n > 0;
    }
}

5.4 private 메서드의 규칙

1. private 키워드 필수
   private void helper() { ... }

2. 본체 작성 필수
   - 추상 X
   - 구현 있어야

3. 외부 노출 X
   - 인터페이스 안에서만 호출
   - 구현체에서도 호출 X
   - 자식 인터페이스에서도 호출 X

4. private static 도 가능
   - static 메서드 간 공유

5.5 private static 메서드

public interface MathUtil {
    
    static int sumOfSquares(int a, int b) {
        return square(a) + square(b);
    }
    
    static int diffOfSquares(int a, int b) {
        return square(a) - square(b);
    }
    
    // ★ Java 9+: private static 메서드
    private static int square(int n) {
        return n * n;
    }
}

// 사용
int s = MathUtil.sumOfSquares(3, 4);
// MathUtil.square(5);   // ❌ 컴파일 에러 (private)

5.6 private vs private static 차이

public interface Demo {
    
    // private 인스턴스 메서드
    // - default 메서드 안에서만 호출
    // - this 사용 가능
    private void instanceHelper() { ... }
    
    // private static 메서드
    // - default 와 static 메서드 양쪽에서 호출
    // - this 사용 불가
    private static void staticHelper() { ... }
    
    default void method1() {
        instanceHelper();    // ✓
        staticHelper();      // ✓
    }
    
    static void method2() {
        // instanceHelper();   // ❌ static 에서 instance 호출 불가
        staticHelper();        // ✓
    }
}

5.7 자바 표준 라이브러리의 예

// java.util.stream.Collectors 같은 곳에 활용
public interface SomeInterface {
    
    default List<String> filterAndSort(List<String> input) {
        return input.stream()
            .filter(this::isValid)
            .sorted(getComparator())
            .collect(Collectors.toList());
    }
    
    default List<String> filterAndCount(List<String> input) {
        long count = input.stream()
            .filter(this::isValid)
            .count();
        return List.of(String.valueOf(count));
    }
    
    private boolean isValid(String s) {
        return s != null && !s.isEmpty();
    }
    
    private Comparator<String> getComparator() {
        return Comparator.naturalOrder();
    }
}

5.8 인터페이스 멤버 종합

Java 9+ 인터페이스의 가능한 멤버:

추상 메서드:
  abstract void m1();   // 또는 그냥 void m1();

default 메서드:
  default void m2() { ... }

static 메서드:
  static void m3() { ... }

private 메서드:
  private void m4() { ... }

private static 메서드:
  private static void m5() { ... }

상수 필드:
  int CONSTANT = 100;   // public static final 자동

중첩 타입:
  interface Inner { ... }
  class Helper { ... }
  enum Type { ... }

→ 인터페이스가 추상클래스와 거의 동등한 표현력.

5.9 자기 점검 답변

Java 9 의 private 메서드가 추가된 이유는?

:
1. 코드 중복 제거:

  • default 메서드 간 공통 로직
  • private 으로 추출 가능
  1. 외부 노출 회피:

    • public default 로 만들면 모든 구현체에 노출
    • private 으로 내부 구현 숨김
  2. API 응집도:

    • 관련 헬퍼가 같은 인터페이스 안에
    • 별도 유틸리티 클래스 불필요
  3. static 메서드 간 공유:

    • private static 으로 static 메서드 공유

→ 인터페이스 표현력의 완성.


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

6.1 Collection 인터페이스의 default

public interface Collection<E> extends Iterable<E> {
    
    // 기존 추상 메서드들
    boolean add(E e);
    boolean remove(Object o);
    int size();
    // ...
    
    // ★ Java 8+ 추가 default 메서드들
    
    default boolean removeIf(Predicate<? super E> filter) {
        Objects.requireNonNull(filter);
        boolean removed = false;
        Iterator<E> each = iterator();
        while (each.hasNext()) {
            if (filter.test(each.next())) {
                each.remove();
                removed = true;
            }
        }
        return removed;
    }
    
    default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }
    
    default Stream<E> parallelStream() {
        return StreamSupport.stream(spliterator(), true);
    }
    
    @Override
    default Spliterator<E> spliterator() {
        return Spliterators.spliterator(this, 0);
    }
}

// 활용
List<Shipment> list = ...;
list.removeIf(s -> s.getStatus() == ShipmentStatus.CANCELLED);
list.stream().filter(...).forEach(...);

6.2 Iterable 의 forEach

public interface Iterable<T> {
    
    Iterator<T> iterator();
    
    // ★ Java 8+
    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
    
    default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }
}

// 활용
List<String> list = ...;
list.forEach(System.out::println);

6.3 Comparator 의 풍부한 default

public interface Comparator<T> {
    
    int compare(T o1, T o2);
    
    // ★ Java 8+ default 메서드들
    
    default Comparator<T> reversed() {
        return Collections.reverseOrder(this);
    }
    
    default Comparator<T> thenComparing(Comparator<? super T> other) {
        Objects.requireNonNull(other);
        return (Comparator<T> & Serializable) (c1, c2) -> {
            int res = compare(c1, c2);
            return (res != 0) ? res : other.compare(c1, c2);
        };
    }
    
    default <U extends Comparable<? super U>> Comparator<T> thenComparing(
            Function<? super T, ? extends U> keyExtractor) {
        return thenComparing(comparing(keyExtractor));
    }
    
    default Comparator<T> thenComparingInt(ToIntFunction<? super T> keyExtractor) {
        return thenComparing(comparingInt(keyExtractor));
    }
    
    // ★ Java 8+ static 메서드들
    
    static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {
        return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE;
    }
    
    static <T> Comparator<T> reverseOrder() {
        return Collections.reverseOrder();
    }
    
    static <T, U extends Comparable<? super U>> Comparator<T> comparing(
            Function<? super T, ? extends U> keyExtractor) {
        Objects.requireNonNull(keyExtractor);
        return (Comparator<T> & Serializable) 
            (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
    }
    
    static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor) { ... }
}

// 강력한 활용
List<Shipment> shipments = ...;

// 단일 정렬
shipments.sort(Comparator.comparing(Shipment::getWeight));

// 복합 정렬
shipments.sort(
    Comparator.comparing(Shipment::getCreatedAt)
        .thenComparing(Shipment::getWeight)
        .thenComparing(Comparator.comparing(Shipment::getBlNo).reversed())
);

→ Comparator 가 함수형 인터페이스 + 풍부한 default 의 모범.

6.4 Map 인터페이스의 default

public interface Map<K, V> {
    
    // 기존
    V get(Object key);
    V put(K key, V value);
    
    // ★ Java 8+ default
    
    default V getOrDefault(Object key, V defaultValue) {
        V v;
        return (((v = get(key)) != null) || containsKey(key))
            ? v : defaultValue;
    }
    
    default V putIfAbsent(K key, V value) {
        V v = get(key);
        if (v == null) {
            v = put(key, value);
        }
        return v;
    }
    
    default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
        V v;
        if ((v = get(key)) == null) {
            V newValue = mappingFunction.apply(key);
            if (newValue != null) {
                put(key, newValue);
                return newValue;
            }
        }
        return v;
    }
    
    default V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
        V oldValue = get(key);
        V newValue = (oldValue == null) ? value : remappingFunction.apply(oldValue, value);
        if (newValue == null) {
            remove(key);
        } else {
            put(key, newValue);
        }
        return newValue;
    }
    
    default void forEach(BiConsumer<? super K, ? super V> action) {
        for (Map.Entry<K, V> entry : entrySet()) {
            action.accept(entry.getKey(), entry.getValue());
        }
    }
    
    // ... 더 많은 default
}

// 활용 — 캐싱 패턴
Map<Long, Shipment> cache = new HashMap<>();
Shipment s = cache.computeIfAbsent(id, this::loadFromDb);

// 빈도 카운팅
Map<String, Long> counts = new HashMap<>();
counts.merge(key, 1L, Long::sum);

6.5 List 의 default

public interface List<E> extends Collection<E> {
    
    // ★ Java 8+
    default void replaceAll(UnaryOperator<E> operator) {
        Objects.requireNonNull(operator);
        final ListIterator<E> li = this.listIterator();
        while (li.hasNext()) {
            li.set(operator.apply(li.next()));
        }
    }
    
    default void sort(Comparator<? super E> c) {
        Object[] a = this.toArray();
        Arrays.sort(a, (Comparator) c);
        ListIterator<E> i = this.listIterator();
        for (Object e : a) {
            i.next();
            i.set((E) e);
        }
    }
}

// 활용
List<String> list = new ArrayList<>(List.of("a", "b", "c"));
list.replaceAll(String::toUpperCase);
// 결과: [A, B, C]

list.sort(Comparator.reverseOrder());
// 결과: [C, B, A]

6.6 Stream 의 default + static 조합

public interface Stream<T> extends BaseStream<T, Stream<T>> {
    
    // 추상
    Stream<T> filter(Predicate<? super T> predicate);
    <R> Stream<R> map(Function<? super T, ? extends R> mapper);
    // ...
    
    // ★ static 팩토리
    static <T> Stream<T> of(T... values) { ... }
    static <T> Stream<T> empty() { ... }
    static <T> Stream<T> generate(Supplier<? extends T> s) { ... }
    static <T> Stream<T> iterate(T seed, UnaryOperator<T> f) { ... }
}

// 사용
Stream.of(1, 2, 3, 4, 5)
    .filter(n -> n > 2)
    .map(n -> n * n)
    .forEach(System.out::println);

6.7 Optional 의 default

public final class Optional<T> {
    // (실제로는 class, 인터페이스 아님)
    // 하지만 비슷한 패턴
    
    public static <T> Optional<T> of(T value) { ... }
    public static <T> Optional<T> empty() { ... }
    public static <T> Optional<T> ofNullable(T value) { ... }
    
    public boolean isPresent() { ... }
    public T get() { ... }
    public T orElse(T other) { ... }
    public T orElseGet(Supplier<? extends T> supplier) { ... }
    public T orElseThrow() { ... }
}

6.8 ILIC 활용 예

@Service
public class ShipmentService {
    
    private final Map<Long, Shipment> cache = new ConcurrentHashMap<>();
    
    public Shipment getOrLoad(Long id) {
        // Map.computeIfAbsent — default 메서드 활용
        return cache.computeIfAbsent(id, this::loadFromDb);
    }
    
    public List<Shipment> getActive() {
        // Collection.stream + Stream.filter + Collectors.toList
        return repository.findAll().stream()
            .filter(s -> s.getStatus() == ShipmentStatus.ACTIVE)
            .toList();
    }
    
    public void cleanupExpired() {
        // Collection.removeIf — default
        cache.values().removeIf(Shipment::isExpired);
    }
    
    public Map<String, Long> countByRoute(List<Shipment> shipments) {
        Map<String, Long> counts = new HashMap<>();
        shipments.forEach(s -> 
            counts.merge(s.getRoute(), 1L, Long::sum)
        );
        return counts;
    }
    
    public List<Shipment> sorted(List<Shipment> shipments) {
        return shipments.stream()
            .sorted(
                Comparator.comparing(Shipment::getPriority).reversed()
                    .thenComparing(Shipment::getCreatedAt)
            )
            .toList();
    }
}

→ default + static 메서드가 모든 자바 코드의 핵심.

6.9 자기 점검 답변

Java 8+ 표준 라이브러리의 default 메서드 활용 5가지는?

:
1. Collection.removeIf, stream, forEach — 람다 순회/필터
2. Comparator.reversed, thenComparing — 복합 정렬
3. Map.computeIfAbsent, merge, getOrDefault — 안전한 캐싱
4. List.replaceAll, sort — 컬렉션 조작
5. Stream.of, empty (static) — 팩토리 메서드

→ 거의 모든 자바 코드의 토대.


7️⃣ default vs 추상클래스 구현 메서드

7.1 비교 표

항목default 메서드추상클래스 구현 메서드
위치인터페이스추상클래스
키워드default(없음)
다중 상속✓ (인터페이스 다중 구현)❌ (단일 상속)
인스턴스 필드 접근❌ (필드 X)
생성자
접근 제어자public 자동자유 (private/protected/public)
상태 보유
Diamond 충돌가능 (명시적 해결)단일 상속이라 없음

7.2 표현력 비교

// 인터페이스 + default
public interface Greeter {
    String getName();   // 추상
    
    default String greet() {
        return "Hello, " + getName();
    }
}

// 추상클래스 + 구현 메서드
public abstract class AbstractGreeter {
    protected String name;   // ← 인스턴스 필드
    
    public AbstractGreeter(String name) {   // ← 생성자
        this.name = name;
    }
    
    public abstract String getTitle();
    
    public String greet() {
        return "Hello, " + getTitle() + " " + name;
    }
}

차이:

  • 인터페이스: getName() 추상 메서드로 우회
  • 추상클래스: 인스턴스 필드 직접 사용
  • 인터페이스는 상태 X, 추상클래스는 상태 보유

7.3 코드 재사용 비교

// 동일한 동작을 인터페이스 vs 추상클래스로

// 1. 인터페이스 + default
public interface Animal {
    String getName();
    
    default void greet() {
        System.out.println("Hi, I'm " + getName());
    }
}

public class Dog implements Animal {
    private String name;
    
    public Dog(String name) { this.name = name; }
    
    @Override
    public String getName() { return name; }
    
    // greet() 자동 사용
}

// 2. 추상클래스
public abstract class AnimalAbstract {
    protected String name;
    
    public AnimalAbstract(String name) { this.name = name; }
    
    public void greet() {
        System.out.println("Hi, I'm " + name);
    }
}

public class Cat extends AnimalAbstract {
    public Cat(String name) { super(name); }
    // greet() 자동 사용
}

두 방식 모두 가능. 선택 기준:

  • 다중 분류 필요 (Swimmer, Flyer) → 인터페이스
  • 강한 is-a + 상태 필요 → 추상클래스

7.4 default 의 한계

// ❌ default 에 인스턴스 필드 추가 시도
public interface Counter {
    int count;   // 컴파일 에러 (public static final 강제)
    
    default void increment() {
        count++;   // 불가
    }
}

// 해결: 추상 메서드로 우회
public interface Counter {
    int getCount();
    void setCount(int n);
    
    default void increment() {
        setCount(getCount() + 1);
    }
}

// 또는 추상클래스 선택
public abstract class AbstractCounter {
    protected int count = 0;
    
    public void increment() {
        count++;
    }
}

→ 상태가 필요하면 추상클래스가 자연스러움.

7.5 다중 행위 vs 단일 분류

// 시나리오: Duck 은 Animal + Swimmer + Flyer

// 인터페이스 활용 (가능)
public interface Animal { String name(); }
public interface Swimmer { default void swim() { ... } }
public interface Flyer { default void fly() { ... } }

public class Duck implements Animal, Swimmer, Flyer {
    private String name;
    
    @Override public String name() { return name; }
    // swim, fly 자동
}

// 추상클래스만으론 어려움
public abstract class Animal { ... }
public abstract class Swimmer { ... }
public abstract class Flyer { ... }

public class Duck extends Animal {
    // Swimmer, Flyer 도 extends?
    // ❌ 단일 상속!
    
    // 우회: 컴포지션
    private Swimmer swimmer = new BasicSwimmer();
    private Flyer flyer = new BasicFlyer();
}

→ 능력 조합은 인터페이스 + default 가 자연스러움.

7.6 선택 가이드

default 메서드 (인터페이스):
  ✓ 능력 표현 (can-do)
  ✓ 다중 구현 필요
  ✓ 상태 없음 (또는 추상 메서드로 우회)
  ✓ 기존 인터페이스에 추가
  ✓ 람다와 함께

추상클래스:
  ✓ "is-a" 강한 관계
  ✓ 공통 상태 (필드) 보유
  ✓ 생성자 필요
  ✓ protected/package 가시성 활용
  ✓ Template Method 패턴
  ✓ 모든 자식이 같은 토대

7.7 자기 점검 답변

default 메서드와 추상클래스 구현 메서드의 가장 큰 차이는?

:
1. 상태 보유:

  • default: 인스턴스 필드 X
  • 추상클래스: 필드 보유 가능
  1. 상속 방식:

    • default: 다중 구현
    • 추상클래스: 단일 상속
  2. 의도:

    • default: 능력 추가 + 호환성
    • 추상클래스: 부분 구현 + Template Method
  3. 표현력:

    • default: 메서드만 + private 헬퍼
    • 추상클래스: 모든 클래스 기능 (필드, 생성자, 다양한 가시성)

→ 추상클래스가 더 풍부, default 는 더 유연.


8️⃣ default 메서드의 단점과 주의

8.1 단점 1 — 의도하지 않은 동작

public interface List<E> {
    
    default void sort(Comparator<? super E> c) {
        // 기본 구현
    }
}

// 기존 코드
public class MyList<E> implements List<E> {
    // sort 안 만들었음 → default 사용
}

// Java 8 업그레이드 후
MyList<String> list = ...;
list.sort(Comparator.naturalOrder());
// default 가 자동 호출됨
// 의도하지 않은 동작 가능 (예: 멀티스레드 안전 X)

문제:

  • 라이브러리 업그레이드 시 default 메서드 자동 적용
  • 기존 코드의 행동 변화 가능
  • 테스트 부담

8.2 단점 2 — 상태 의존 회피

// ❌ default 에서 상태 접근 시도
public interface Counter {
    
    // 추상 메서드
    int getValue();
    void setValue(int v);
    
    // default 메서드
    default void increment() {
        // 상태에 접근하려면 추상 메서드로 우회 필요
        setValue(getValue() + 1);
        // setValue, getValue 호출이 매번
        // 추상클래스라면 count++ 한 줄
    }
}

문제:

  • 인터페이스에 필드 없음
  • 모든 상태 접근이 메서드 호출
  • 코드 장황 + 약간의 오버헤드

8.3 단점 3 — Diamond Problem 부담

public interface A {
    default void m() { System.out.println("A"); }
}

public interface B {
    default void m() { System.out.println("B"); }
}

public class C implements A, B {
    // ❌ 컴파일 에러
    // class C inherits unrelated defaults for m() from types A and B
    
    // 해결: 명시적 오버라이드
    @Override
    public void m() {
        A.super.m();
    }
}

문제:

  • 인터페이스 추가/변경 시 충돌 가능
  • 명시적 해결 필요
  • 라이브러리 진화 시 신중

8.4 단점 4 — 디버깅 어려움

public interface MyInterface {
    default void process() {
        helper1();
        helper2();
    }
    
    default void helper1() { ... }
    default void helper2() { ... }
}

public class MyClass implements MyInterface {
    @Override
    public void helper1() {
        // 오버라이드
    }
}

MyClass m = new MyClass();
m.process();
// 디버깅 시:
// - process() 는 어디서?
// - helper1() 는 오버라이드 됨
// - helper2() 는 인터페이스 default
// - 추적이 복잡

8.5 주의 1 — 인터페이스 진화 신중

// Java 8 출시 시 Collection 에 추가된 default 들
// → 충분히 검토 후 추가

// 일반적 권장:
// - 정말 필요한 default 만 추가
// - 명확한 기본 동작
// - 모든 구현체에 안전한 동작

8.6 주의 2 — 테스트 부담

public interface Calculator {
    int add(int a, int b);
    
    default int multiply(int a, int b) {
        // 기본 구현
        int result = 0;
        for (int i = 0; i < b; i++) {
            result = add(result, a);
        }
        return result;
    }
}

// 테스트:
// - add() 만 구현하는 클래스의 multiply() 도 테스트
// - default 가 모든 구현체에서 동작 확인
// - 테스트 부담 ↑

8.7 주의 3 — 멀티스레드 안전성

public interface ThreadUnsafeContainer<E> {
    
    // 추상 메서드
    void add(E e);
    boolean remove(E e);
    
    // default 메서드
    default void addAll(Collection<? extends E> c) {
        // ❌ 멀티스레드 안전 X
        // 다른 스레드가 add 중에 새 요소 추가하면?
        for (E e : c) {
            add(e);
        }
    }
}

문제:

  • default 가 멀티스레드 환경 고려 못 함
  • 구현체가 멀티스레드 안전이어도 default 는 X
  • 명시적 동기화 또는 오버라이드 필요

8.8 주의 4 — 추상 메서드와 같은 시그니처

public interface MyInterface {
    
    // 같은 시그니처 두 번 — 한 쪽은 추상, 한 쪽은 default
    // ❌ 컴파일 에러
    void m();
    default void m() { }   // 같은 시그니처
}

→ 같은 인터페이스에서 같은 시그니처 충돌.

8.9 default 사용 가이드

권장:
  ✓ 편의 메서드 (실제 동작은 추상 메서드 활용)
  ✓ 기존 인터페이스의 호환 유지
  ✓ 함수형 인터페이스의 조합 메서드 (and, or, then)
  ✓ 명확하고 단순한 기본 구현

회피:
  ✗ 복잡한 비즈니스 로직
  ✗ 상태에 강하게 의존하는 로직
  ✗ 멀티스레드 안전 보장 필요 시
  ✗ Diamond 위험 높은 시그니처
  ✗ 추상클래스로 더 자연스러운 경우

→ default 는 도구, 만능 아님.

8.10 자기 점검 답변

default 메서드 사용 시 주의 사항 3가지는?

:
1. 상태 의존 회피:

  • 인스턴스 필드 없으니 추상 메서드로 우회
  • 코드 장황해질 수 있음
  1. Diamond Problem 검토:

    • 다른 인터페이스와 시그니처 충돌 가능
    • 명시적 해결 필요
  2. 멀티스레드 안전성:

    • default 는 동기화 X
    • 멀티스레드 환경에선 별도 검토

추가:

  • 라이브러리 진화 시 영향 검토
  • 단순한 default 권장
  • 추상클래스가 더 적합한 경우 고려

9️⃣ 면접 + 자기 점검

9.1 면접 단골 질문 매핑

Q핵심 답변
default 메서드의 등장 이유?하위 호환성 + Stream API 도입
default 메서드 문법?default 키워드 + 본체
Diamond Problem 부활?Java 8 default 메서드로
Diamond 해결 규칙 3가지?클래스 우선, 구체적 우선, 명시적
인터페이스 static 메서드?인터페이스명으로 호출, 상속 안 됨
Java 9 private 메서드 이유?default 간 코드 공유, 외부 노출 회피
default vs 추상클래스 구현 차이?상태 X, 다중 구현, 가시성 public
Collection.stream 가능 이유?default 메서드로 추가
Comparator.reversed/thenComparing?default 메서드 조합
Map.computeIfAbsent?default 안전한 캐싱
default 의 위험?의도하지 않은 동작, Diamond, 멀티스레드

9.2 자기 점검 체크리스트

기본 이해

  • default 메서드의 등장 배경을 안다
  • 하위 호환성 문제를 설명할 수 있다
  • default 메서드의 4가지 규칙을 안다
  • 오버라이드 패턴을 안다 (super 호출 포함)
  • 자동 public 임을 안다

Diamond Problem

  • Diamond Problem 의 부활을 안다
  • 3가지 해결 규칙을 안다 (클래스/구체/명시)
  • X.super.method() 문법을 안다
  • 충돌 시 명시적 오버라이드 가능
  • 복잡한 다이아몬드 시나리오 처리

static / private 메서드

  • static 메서드 호출 방법 (인터페이스명)
  • static 메서드 상속 X
  • private 메서드 (Java 9+) 의 활용
  • private static 메서드의 차이
  • Java 8+ 인터페이스의 모든 멤버 종류

표준 라이브러리

  • Collection 의 default 5가지 이상
  • Comparator 의 default 활용
  • Map 의 computeIfAbsent, merge 등
  • List/Set 의 of (static) 활용
  • Stream/Optional 의 static 팩토리

default vs 추상클래스

  • 두 방식의 차이를 안다
  • 상태 보유 여부를 안다
  • 다중 구현 vs 단일 상속
  • 선택 가이드 5가지

9.3 추가 심화 질문

Q1: default 메서드는 final 가능한가?

답:

  • ❌ 불가
  • default 는 본질적으로 오버라이드 가능
  • final 과 모순
  • 구현체가 자기 방식 선택 가능해야

Q2: 모든 인터페이스 메서드를 default 로 만들면?

답:

  • 가능하지만 권장 X
  • 추상 메서드가 없으면 인터페이스의 본질 손상
  • 추상 메서드 = "이것은 구현해야 함" 의 강제
  • default 만 있으면 단순 유틸리티
  • 차라리 final class + static 메서드

Q3: default 메서드에서 다른 default 메서드를 호출 가능?

답:

  • 가능
  • 같은 인터페이스 안에서 자유롭게
public interface MyInterface {
    default void method1() {
        method2();   // ✓ 가능
    }
    
    default void method2() {
        System.out.println("2");
    }
}

Q4: 같은 인터페이스를 두 번 implements 하면?

답:

public class C implements MyInterface, MyInterface {
    // ❌ 컴파일 에러 (중복)
}
  • 직접 중복은 컴파일 에러
  • 단, 간접 (extends 하는 인터페이스 + 직접) 은 가능
public interface A { default void m() {} }
public interface B extends A { }
public class C implements A, B {   // OK, 간접 중복
    // A 의 default 사용
}

Q5: default 메서드를 추가하면 ABI 호환?

답:

  • 소스 호환: ✓ (재컴파일 시 OK)
  • 바이너리 호환: ✓ (이전 .class 도 작동)
  • 단, 시그니처 충돌 시 깨질 수 있음
  • 일반적으로 안전한 진화

🎯 핵심 요약 — 3줄 정리

1. default 메서드 = 하위 호환성의 해결

  • 기존 인터페이스에 새 메서드 추가 가능
  • Collection.stream, Comparator.reversed 등
  • Java 8 의 혁명적 변화

2. Diamond Problem 의 부활과 해결

  • default 로 다중 구현 충돌 가능
  • 3가지 규칙: 클래스 우선, 구체 우선, 명시적
  • X.super.method() 로 명시적 호출

3. 인터페이스의 표현력 진화

  • Java 8: default, static
  • Java 9: private, private static
  • 추상클래스와 거의 동등한 표현력
  • 단, 상태 (필드) 는 여전히 X

📚 다음으로...

Unit 4.4 — 추상클래스 vs 인터페이스 선택 기준

이번 Unit에서 default 의 메커니즘을 봤다면, 다음은 선택의 결정적 기준.

  • "is-a" 관계 vs "can-do" 능력
  • Spring, JPA, Stream 의 선택
  • AbstractList + List 패턴
  • 사용자 정의 추상화 설계 가이드
  • Phase 4 졸업

Phase 4 진행 상황

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

3주차 누적 진행

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

총: 16/43 Unit 작성 (약 37%)
profile
Software Developer

0개의 댓글