3주차 Unit 4.2 — 인터페이스의 특징

Psj·2026년 5월 19일

F-lab

목록 보기
89/238

Unit 4.2 — 인터페이스의 특징

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


📌 학습 목표

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

  • interface 키워드 의 정확한 의미는?
  • 인터페이스의 멤버 (메서드/필드) 의 자동 수식자는?
  • 다중 구현 (Multiple Implementation) 이 왜 가능한가? (다중 상속은 안 되는데)
  • 인터페이스에 생성자가 없는 이유 는?
  • 인스턴스 변수 없음, 상수만 가능 의 의미는?
  • 마커 인터페이스 (Marker Interface) 와 함수형 인터페이스 (Functional Interface) 는?
  • Comparable, Iterable, Runnable, Serializable 등의 활용은?
  • 인터페이스 자체는 인스턴스화 불가 인데 람다는 어떻게 사용하나?
  • 인터페이스 vs 추상클래스 의 첫 비교는?

🎯 핵심 한 문장

인터페이스는 "할 수 있는 능력 (can-do)" 을 표현하는 순수한 추상화 도구다.
모든 메서드는 자동으로 public abstract, 모든 필드는 자동으로 public static final.
다중 구현이 가능해서 "어느 분류에도 속할 수 있는 능력" 을 자유롭게 부여 가능.
Java 8+ 부터는 defaultstatic 메서드로 구현도 가능 (다음 Unit).
Comparable, Iterable, Runnable, Serializable 같은 자바 표준 인터페이스가 모든 코드의 토대.

비유 — 자격증과 신분증

일반 클래스 (Concrete Class):
  완성된 사람 — 직업, 능력 모두 갖춤
  Dog → 개로 태어남

추상클래스 (Abstract Class):
  부분 완성 — 일부 능력은 자식이 채워야
  Animal → 동물이지만 sound() 는 자식이

인터페이스 (Interface):
  자격증 / 능력 인증서
  - "수영 가능 (Swimmer)"
  - "비행 가능 (Flyer)"
  - "비교 가능 (Comparable)"
  
  한 사람이 여러 자격증 보유 가능
  Duck = Animal + Swimmer + Flyer

→ 인터페이스 = 능력 표현 + 다중 부여 가능.


🧭 9개 섹션 로드맵

1. interface 키워드와 본질
2. 멤버의 자동 수식자
3. 다중 구현 (Multiple Implementation)
4. 인터페이스의 제약 — 생성자/필드 없음
5. 마커 인터페이스 (Marker Interface)
6. 함수형 인터페이스와 람다
7. 자바 표준 인터페이스 활용
8. 인터페이스의 진화 — 역사
9. 면접 + 자기 점검

1️⃣ interface 키워드와 본질

1.1 interface 의 정의

interface:
  
  "객체가 가져야 할 메서드의 명세 (specification)"
  
  - 메서드 시그니처만 정의 (Java 7 까지)
  - 구현은 없음
  - implements 키워드로 구현

1.2 기본 문법

// 인터페이스 정의
public interface Drawable {
    void draw();
    void erase();
}

// 인터페이스 구현
public class Circle implements Drawable {
    
    @Override
    public void draw() {
        System.out.println("Drawing circle");
    }
    
    @Override
    public void erase() {
        System.out.println("Erasing circle");
    }
}

// 사용
Drawable d = new Circle();
d.draw();

1.3 클래스와 인터페이스의 키워드 차이

// 클래스: class
public class Circle { ... }

// 추상클래스: abstract class
public abstract class Shape { ... }

// 인터페이스: interface (abstract 안 붙임)
public interface Drawable { ... }

// 인터페이스끼리 확장: extends
public interface ColoredDrawable extends Drawable {
    void setColor(Color c);
}

// 클래스가 인터페이스 구현: implements
public class Circle implements Drawable { ... }

// 추상클래스도 인터페이스 구현 가능
public abstract class Shape implements Drawable { ... }

// 클래스 상속 + 인터페이스 구현
public class Circle extends Shape implements Drawable, Cloneable {
}

1.4 인터페이스의 본질

인터페이스는:
  - "is-a" 관계 X
  - "can-do" 능력 표현 ✓
  
예:
  Cat extends Animal       (is-a: 고양이는 동물)
  Cat implements Swimmer  (can-do: 고양이는 수영 가능)

다른 시각:
  추상클래스: "어떤 종류인가?"
  인터페이스: "무엇을 할 수 있는가?"

1.5 인터페이스 vs 추상클래스 — 첫 비교

항목추상클래스인터페이스
키워드abstract classinterface
구현 키워드extendsimplements
다중 상속/구현❌ (단일)✓ (다중)
메서드 구현가능 (구현 + 추상)Java 7 X, Java 8+ default
필드인스턴스 + staticstatic final 만
생성자가능
접근 제어자모두 가능public 만 (자동)
의도"is-a" 관계"can-do" 능력

1.6 코드 예시 — ILIC

// 인터페이스: 운송 가능 능력
public interface Shippable {
    void ship();
    BigDecimal getShippingCost();
}

// 인터페이스: 추적 가능 능력
public interface Trackable {
    String getTrackingNumber();
    TrackingStatus getStatus();
}

// 클래스: 두 능력 모두
public class Shipment implements Shippable, Trackable {
    
    @Override
    public void ship() {
        // ...
    }
    
    @Override
    public BigDecimal getShippingCost() {
        // ...
    }
    
    @Override
    public String getTrackingNumber() {
        // ...
    }
    
    @Override
    public TrackingStatus getStatus() {
        // ...
    }
}

// 다형성 활용
public void processShippable(Shippable s) {
    s.ship();
}

public void processTrackable(Trackable t) {
    System.out.println(t.getTrackingNumber());
}

Shipment shipment = new Shipment();
processShippable(shipment);   // Shippable 로 사용
processTrackable(shipment);   // Trackable 로 사용

1.7 자기 점검 답변

인터페이스와 추상클래스의 가장 본질적 차이는?

:

  • 인터페이스:

    • "can-do" 능력 표현
    • 다중 구현 가능
    • 인스턴스 상태 없음
    • 자유로운 부여 (분류 무관)
  • 추상클래스:

    • "is-a" 관계 표현
    • 단일 상속만
    • 인스턴스 상태 있음 (필드)
    • 강한 결합 (확장 관계)

→ "어떤 종류인가" vs "무엇을 할 수 있는가".


2️⃣ 멤버의 자동 수식자

2.1 메서드의 자동 수식자

public interface Drawable {
    
    // 작성한 코드
    void draw();
    
    // 실제로는 컴파일러가:
    // public abstract void draw();
    
    // 자동 부여:
    // - public
    // - abstract (Java 7 까지)
}

자동 부여되는 수식자:

  • public: 모든 메서드는 public
  • abstract: 본체 없으면 자동 abstract (Java 7)
  • Java 8+ 에선 default, static, private 메서드도 가능

2.2 명시적 수식자 사용

public interface Drawable {
    
    // ✓ public 명시 (불필요하지만 합법)
    public void draw();
    
    // ✓ abstract 명시 (불필요하지만 합법)
    abstract void erase();
    
    // ✓ public abstract 모두 명시
    public abstract void resize();
    
    // ❌ 다른 수식자
    // private void hidden();      ← Java 8 까지 에러, 9+ 부터 가능
    // protected void inner();     ← 에러
    // package void check();       ← 에러 (자동 public)
    // final void cannot();        ← 에러 (abstract 와 모순)
    // static void util();         ← Java 7 까지 에러, 8+ 가능
}

2.3 필드의 자동 수식자

public interface Constants {
    
    // 작성한 코드
    int MAX_VALUE = 100;
    String NAME = "Default";
    
    // 실제로는 컴파일러가:
    // public static final int MAX_VALUE = 100;
    // public static final String NAME = "Default";
    
    // 자동 부여:
    // - public
    // - static
    // - final
}

핵심:

  • public: 외부에서 접근 가능
  • static: 클래스 레벨 (인스턴스 무관)
  • final: 초기화 후 변경 불가 (상수)

2.4 필드의 명시적 수식자

public interface Constants {
    
    // ✓ 명시 (불필요하지만 합법)
    public static final int A = 1;
    
    // ✓ 일부 생략 (자동 추가됨)
    static final int B = 2;       // public 자동
    public final int C = 3;       // static 자동
    public static int D = 4;       // final 자동
    int E = 5;                     // 모두 자동
    
    // ❌ 다른 수식자
    // private int F = 6;          // 에러 (public 자동)
    // int G;                      // 에러 (초기화 필수, final 이라)
}

2.5 인스턴스 필드 불가

public interface Counter {
    
    // ❌ 인스턴스 필드 시도
    int count = 0;
    
    // 실제로는:
    // public static final int count = 0;
    // → 모든 구현체가 공유 + 변경 불가
    // → 인스턴스별 count 가 아님
}

// 구현체에서 인스턴스 필드 가능
public class CounterImpl implements Counter {
    private int instanceCount = 0;   // 인스턴스 필드 OK
}

→ 인터페이스에는 상수만.
→ 인스턴스 상태는 구현 클래스에서.

2.6 자동 수식자의 의도

왜 public 자동?
  - 인터페이스는 공개 명세
  - 외부에서 접근하기 위한 것
  - 다른 가시성은 의미 없음

왜 abstract 자동? (Java 7 까지)
  - 인터페이스의 본질
  - 구현 없는 명세

왜 static 자동? (필드)
  - 인터페이스는 인스턴스 없음
  - 상수만 가능
  - 클래스 레벨 데이터

왜 final 자동? (필드)
  - 모든 구현체가 공유
  - 변경되면 일관성 깨짐
  - 상수로 강제

2.7 자기 점검 답변

인터페이스 메서드와 필드의 자동 수식자는?

:

  • 메서드: public abstract (Java 7 까지)

    • public 자동
    • 본체 없으면 abstract 자동
    • Java 8+: default, static, private 도 가능
  • 필드: public static final

    • public 자동 (공개)
    • static 자동 (클래스 레벨)
    • final 자동 (상수)
    • 초기화 필수

→ 인터페이스의 본질이 키워드에 반영됨.


3️⃣ 다중 구현 (Multiple Implementation)

3.1 다중 구현의 정의

다중 구현 (Multiple Implementation):
  
  한 클래스가 여러 인터페이스를 동시에 구현.
  
문법:
  public class MyClass implements A, B, C { ... }
  
효과:
  - MyClass 는 A 이면서 B 이면서 C
  - 어떤 메서드도 모호함 없음 (인터페이스는 구현 없으니까)

3.2 클래스 다중 상속과의 차이

클래스 다중 상속 (자바엔 없음):
  class Bat extends Mammal, Bird { ... }   // ❌
  
  문제: Diamond Problem
  - Mammal.move() 와 Bird.move() 중 어느 것?
  
인터페이스 다중 구현:
  class Bat implements Flyer, Mammal_Interface { ... }   // ✓
  
  모호함 없음:
  - 인터페이스 메서드는 구현 없음
  - 모두 클래스가 직접 구현
  - 메서드 해결 명확

3.3 코드 예시

public interface Swimmer {
    void swim();
}

public interface Flyer {
    void fly();
}

public interface Walker {
    void walk();
}

// 단일 구현
public class Fish implements Swimmer {
    @Override
    public void swim() { ... }
}

// 다중 구현
public class Duck implements Swimmer, Flyer, Walker {
    @Override
    public void swim() { ... }
    
    @Override
    public void fly() { ... }
    
    @Override
    public void walk() { ... }
}

// 사용
Swimmer s = new Duck();
s.swim();   // Duck.swim()

Flyer f = new Duck();
f.fly();    // Duck.fly()

// 같은 객체, 다른 타입으로 사용 가능
Duck duck = new Duck();
swim(duck);   // Swimmer 매개변수
fly(duck);    // Flyer 매개변수

3.4 인터페이스끼리의 다중 상속

// 인터페이스끼리는 extends + 다중 가능
public interface SwimFly extends Swimmer, Flyer {
    void splash();   // 추가 메서드
}

// 구현
public class Duck implements SwimFly {
    @Override
    public void swim() { ... }
    
    @Override
    public void fly() { ... }
    
    @Override
    public void splash() { ... }
}

3.5 다중 구현의 활용

// 자바 표준 라이브러리의 예
public class ArrayList<E> 
        extends AbstractList<E>     // 추상클래스 단일 상속
        implements 
            List<E>,                // 리스트 능력
            RandomAccess,           // 인덱스 빠른 접근 (마커)
            Cloneable,              // 복제 가능
            java.io.Serializable {  // 직렬화 가능
    // ...
}

ArrayList 는:

  • AbstractList 를 상속 (구현 공유)
  • 4가지 인터페이스 구현 (능력 표현)

3.6 인터페이스 다중 구현의 제약

// ❌ 같은 메서드 시그니처에 다른 반환 타입
public interface A {
    int getValue();
}

public interface B {
    String getValue();   // 같은 이름, 다른 반환
}

public class C implements A, B {
    // 어느 것 구현?
    // 컴파일 에러
}

// ✓ 같은 시그니처면 OK
public interface A {
    void process();
}

public interface B {
    void process();   // 같은 시그니처
}

public class C implements A, B {
    @Override
    public void process() {
        // 두 인터페이스 모두 만족
    }
}

3.7 다중 구현의 의미

다중 구현의 가치:

1. 능력의 자유로운 조합
   Duck = Swimmer + Flyer + Walker
   각 능력은 독립적

2. 다양한 시각으로 객체 사용
   같은 객체를 여러 매개변수 타입으로

3. 코드 분류의 유연성
   하나의 분류 (extends) + 여러 능력 (implements)

4. 표준 인터페이스 활용
   Comparable, Iterable 등을 부여

3.8 자기 점검 답변

클래스는 단일 상속만 되지만 인터페이스는 다중 구현이 가능한 이유는?

:

  • 클래스 다중 상속의 문제:

    • Diamond Problem
    • Mammal.move() vs Bird.move() 모호함
    • 자바는 단일 상속 채택
  • 인터페이스 다중 구현이 가능한 이유:

    • 인터페이스는 구현 없음 (Java 7 까지)
    • 모든 메서드를 클래스가 직접 구현
    • 모호함 자체가 발생 안 함
    • 메서드 해결 명확
  • Java 8+ default 메서드 등장 후:

    • 일부 모호함 가능
    • 다음 Unit 4.3 에서 자세히

→ "구현이 없으면 충돌도 없다".


4️⃣ 인터페이스의 제약 — 생성자/필드 없음

4.1 생성자가 없는 이유

public interface Drawable {
    
    // ❌ 생성자 시도
    public Drawable() {
        // 에러: 인터페이스에 생성자 불가
    }
}

이유:

1. 인터페이스는 인스턴스화 불가
   - 추상클래스도 인스턴스화 불가지만 자식이 super() 호출
   - 인터페이스는 그런 호출 자체가 없음

2. 인터페이스는 상태 없음
   - 인스턴스 필드 없음
   - 초기화할 게 없음

3. 다중 구현의 모호함
   - 여러 인터페이스 생성자 호출 순서?
   - 다중 상속 Diamond 와 같은 문제

4. 의도의 차이
   - 인터페이스 = 능력 명세
   - 생성자 = 객체 생성 (의미 다름)

4.2 인스턴스 필드 불가

public interface Counter {
    
    // ❌ 인스턴스 필드 시도
    private int count;
    
    // 실제로는:
    // public static final int count = ???;
    // → static, 초기화 필수, 변경 불가
    
    // 인터페이스에 인스턴스 필드 불가능
}

이유:

1. 다중 구현의 모호함
   - 여러 인터페이스에 같은 이름 필드?
   - 인스턴스 메모리에 어떻게 배치?

2. 상태와 동작의 분리
   - 인터페이스 = 동작 명세
   - 상태는 구현 클래스에서

3. 자바 설계 철학
   - 인터페이스는 행위 위주
   - 상태는 클래스의 영역

4.3 상수는 가능

public interface FreightPolicy {
    
    // ✓ 상수 (public static final 자동)
    BigDecimal FUEL_RATE = new BigDecimal("0.05");
    int MAX_WEIGHT = 10000;
    String DEFAULT_CURRENCY = "USD";
    
    Set<String> SUPPORTED_PORTS = Set.of(
        "BUSAN", "INCHEON", "GWANGYANG"
    );
}

// 사용
BigDecimal rate = FreightPolicy.FUEL_RATE;
int max = FreightPolicy.MAX_WEIGHT;

// 구현체에서도 사용
public class StandardPolicy implements FreightPolicy {
    public void check(int weight) {
        if (weight > MAX_WEIGHT) {   // 상수 접근 (this 없이)
            throw new IllegalArgumentException();
        }
    }
}

4.4 상수 인터페이스 안티패턴

// ❌ 안티패턴: 상수만 모아둔 인터페이스
public interface Constants {
    int MAX = 100;
    String NAME = "default";
    // ...
}

// 사용
public class MyClass implements Constants {
    void method() {
        System.out.println(MAX);   // implements 만으로 접근
    }
}

문제:

  • "능력" 이 아닌 상수 모음을 implements 로 노출
  • 캡슐화 위반
  • 인터페이스의 본질과 어긋남

해결:

// ✓ 상수 클래스
public final class Constants {
    private Constants() {}   // 인스턴스화 방지
    
    public static final int MAX = 100;
    public static final String NAME = "default";
}

// 사용
int max = Constants.MAX;

→ Java 5+ 의 static import 도 활용 가능.

4.5 인터페이스의 가능한 멤버

public interface MyInterface {
    
    // 1. 추상 메서드 (Java 7 부터)
    void method1();
    
    // 2. 상수 (public static final)
    int CONSTANT = 100;
    
    // Java 8+ 추가:
    
    // 3. default 메서드 (구현 가능)
    default void method2() {
        System.out.println("Default");
    }
    
    // 4. static 메서드
    static void method3() {
        System.out.println("Static");
    }
    
    // Java 9+ 추가:
    
    // 5. private 메서드
    private void method4() {
        System.out.println("Private");
    }
    
    // 6. private static 메서드
    private static void method5() {
        System.out.println("Private static");
    }
    
    // 가능한 멤버:
    // - 추상 메서드
    // - 상수
    // - default 메서드 (Java 8+)
    // - static 메서드 (Java 8+)
    // - private 메서드 (Java 9+)
    // - private static 메서드 (Java 9+)
    
    // 불가능:
    // - 인스턴스 필드
    // - 생성자
    // - 인스턴스 초기화 블록
    // - protected, package-private 메서드 (default 키워드와 다름)
}

4.6 nested 타입은 가능

public interface Container {
    
    void add(Object item);
    
    // 중첩 인터페이스
    interface Listener {
        void onAdded(Object item);
    }
    
    // 중첩 클래스
    class Helper {
        static void log(Object item) {
            System.out.println(item);
        }
    }
    
    // 중첩 enum
    enum Type {
        SMALL, MEDIUM, LARGE
    }
}

// 사용
Container.Listener listener = ...;
Container.Type type = Container.Type.LARGE;
Container.Helper.log("hello");

중첩 타입은 자동으로 public static.

4.7 자기 점검 답변

인터페이스에 생성자가 없는 이유 4가지는?

:
1. 인스턴스화 불가 — 생성자 호출 자체가 없음
2. 상태 없음 — 인스턴스 필드 없으니 초기화할 게 없음
3. 다중 구현의 모호함 — 여러 인터페이스 생성자 호출 순서?
4. 의도의 차이 — 인터페이스는 능력 명세, 생성자는 객체 생성

추가:

  • 인스턴스 필드 불가 → 상수만 가능
  • 상수 인터페이스 안티패턴 회피
  • 상태 + 동작 분리

5️⃣ 마커 인터페이스 (Marker Interface)

5.1 마커 인터페이스의 정의

마커 인터페이스 (Marker Interface):

  메서드가 하나도 없는 인터페이스.
  단지 "이 클래스는 X 의 성질을 가진다" 를 표시.

영어:
  "Tagging Interface" 라고도 함.

5.2 자바의 대표적 마커 인터페이스

// 1. Serializable
public interface Serializable {
    // 메서드 없음
    // 의미: "이 객체는 직렬화 가능"
}

// 2. Cloneable
public interface Cloneable {
    // 메서드 없음
    // 의미: "Object.clone() 호출 가능"
}

// 3. RandomAccess
public interface RandomAccess {
    // 메서드 없음
    // 의미: "인덱스 접근이 빠름 (O(1))"
}

// 4. Remote (RMI)
public interface Remote {
    // 메서드 없음
    // 의미: "원격 호출 가능 객체"
}

5.3 마커 인터페이스의 동작 메커니즘

// 마커 사용 예 — Serializable
public class Shipment implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private Long id;
    private String blNo;
    // ...
}

// 직렬화 로직
public void serialize(Object obj, OutputStream out) throws IOException {
    if (!(obj instanceof Serializable)) {
        throw new NotSerializableException(obj.getClass().getName());
    }
    
    // 실제 직렬화
    ObjectOutputStream oos = new ObjectOutputStream(out);
    oos.writeObject(obj);
}

핵심:

  • 메서드 호출 X
  • instanceof 로 마커 검사
  • 마커 있으면 특별한 처리

5.4 RandomAccess 의 실제 사용

// Collections.binarySearch 내부
public static <T> int binarySearch(List<T> list, T key, Comparator<? super T> c) {
    
    if (list instanceof RandomAccess) {
        // 인덱스 접근 빠름 → 일반 이진 검색
        return indexedBinarySearch(list, key, c);
    } else {
        // 인덱스 접근 느림 → Iterator 기반 이진 검색
        return iteratorBinarySearch(list, key, c);
    }
}
  • ArrayList: implements RandomAccess → 빠른 경로
  • LinkedList: 미구현 → 느린 경로

→ 마커가 런타임 알고리즘 분기.

5.5 마커 인터페이스의 장점

1. 컴파일 타임 검증
   - instanceof 로 명확
   - 잘못된 사용 사전 차단

2. 단순성
   - 메서드 없음 → 구현 부담 X
   - 단지 표시만

3. 다중 마커 가능
   - 한 클래스가 여러 마커 보유
   - implements Serializable, Cloneable, ...

4. 동적 검사
   - 런타임에 if (obj instanceof Marker) ...
   - 유연한 처리

5.6 마커 인터페이스의 단점

1. 정적 검사만 가능
   - 추가 정보 (메타데이터) X
   - "표시" 외 의미 없음

2. 어노테이션 등장 (Java 5+)
   - 더 풍부한 메타데이터
   - @Deprecated, @Override, @SuppressWarnings 등
   - 마커 인터페이스 일부 대체

3. 런타임 의존
   - instanceof 검사 필요
   - 컴파일 타임 다형성 활용 X

5.7 어노테이션 vs 마커 인터페이스

// 마커 인터페이스
public class Shipment implements Serializable {
    // ...
}

// 어노테이션
@Entity
@Table(name = "shipments")
public class Shipment {
    // ...
}

비교:
  마커 인터페이스:
    ✓ 컴파일 타임 타입 검사
    ✓ instanceof 사용
    ✗ 메타데이터 제한 (단순 표시)
  
  어노테이션:
    ✓ 풍부한 메타데이터 (속성,)
    ✓ 메서드, 필드 등 다양한 위치
    ✗ 런타임 검사만 (보통)
    ✗ 타입 시스템 통합 X

선택:

  • "이 타입에 속한다" → 마커 인터페이스
  • "이 메타데이터를 가진다" → 어노테이션

5.8 ILIC 사용 예 — 직렬화

// 도메인 객체
public class Shipment implements Serializable {
    
    private static final long serialVersionUID = 20251019L;
    
    private Long id;
    private String blNo;
    private BigDecimal weight;
    
    // ...
}

// 사용
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ship.dat"));
oos.writeObject(shipment);   // Serializable 이므로 OK

// 비 Serializable 객체
public class NotSerializable {
    private int value;
}

oos.writeObject(new NotSerializable());   // ❌ NotSerializableException

5.9 사용자 정의 마커

// 도메인 마커
public interface AuditableEntity {
    // 메서드 없음
    // 의미: "이 엔티티는 변경 감사를 받는다"
}

public interface CacheableEntity {
    // 의미: "이 엔티티는 캐시 가능"
}

// 구현
public class Shipment implements AuditableEntity, CacheableEntity {
    // ...
}

// 일반 처리 로직
public void save(Object entity) {
    if (entity instanceof AuditableEntity) {
        recordAudit(entity);
    }
    
    if (entity instanceof CacheableEntity) {
        cache.put(getId(entity), entity);
    }
    
    repository.save(entity);
}

5.10 자기 점검 답변

마커 인터페이스와 어노테이션 중 어느 것을 언제 쓰나?

:

  • 마커 인터페이스 선택:

    • 타입 시스템 통합 필요
    • instanceof 로 분기
    • 런타임 다형성 활용
    • 예: Serializable, RandomAccess
  • 어노테이션 선택:

    • 메타데이터 풍부 (속성, 값)
    • 메서드/필드/매개변수에 적용
    • 컴파일 도구 활용 (Spring, JPA)
    • 예: @Entity, @Service

자바 라이브러리의 변화:

  • Java 1.0 ~ 1.4: 마커 인터페이스
  • Java 5+: 어노테이션 도입
  • 현재: 두 방식 공존

6️⃣ 함수형 인터페이스와 람다

6.1 함수형 인터페이스의 정의

함수형 인터페이스 (Functional Interface):

  추상 메서드가 정확히 하나인 인터페이스.
  (default, static 메서드는 무관)

영어:
  "Single Abstract Method (SAM)" 인터페이스

6.2 @FunctionalInterface 어노테이션

@FunctionalInterface
public interface Runnable {
    void run();
}

@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
    
    // default 메서드 — 무관
    default Comparator<T> reversed() {
        return (a, b) -> compare(b, a);
    }
    
    // static 메서드 — 무관
    static <T> Comparator<T> naturalOrder() { ... }
}

@FunctionalInterface:

  • 검증 어노테이션
  • 추상 메서드가 2개 이상이면 컴파일 에러
  • 의도 명확화

6.3 람다 표현식 (Lambda Expression)

// Java 7 까지: 익명 클래스
Runnable r = new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello");
    }
};

// Java 8+: 람다
Runnable r = () -> System.out.println("Hello");

// 더 자세한 람다
Runnable r = () -> {
    System.out.println("Hello");
    System.out.println("World");
};

핵심:

  • 함수형 인터페이스를 람다로 표현
  • 코드 간결화
  • 함수형 프로그래밍 활용

6.4 람다의 추론

// Comparator 사용
List<String> list = new ArrayList<>(List.of("banana", "apple", "cherry"));

// 익명 클래스
Collections.sort(list, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
});

// 람다
Collections.sort(list, (a, b) -> a.length() - b.length());

// 메서드 참조
Collections.sort(list, Comparator.comparingInt(String::length));

6.5 자바 표준 함수형 인터페이스

// java.util.function 패키지

// 1. Function<T, R> — 입력 T, 출력 R
Function<String, Integer> length = s -> s.length();
length.apply("hello");   // 5

// 2. Predicate<T> — 입력 T, 출력 boolean
Predicate<String> isEmpty = String::isEmpty;
isEmpty.test("");   // true

// 3. Consumer<T> — 입력 T, 출력 없음
Consumer<String> printer = System.out::println;
printer.accept("hello");   // 출력: hello

// 4. Supplier<T> — 입력 없음, 출력 T
Supplier<List<String>> listSupplier = ArrayList::new;
List<String> list = listSupplier.get();

// 5. BiFunction<T, U, R> — 입력 T, U, 출력 R
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
add.apply(2, 3);   // 5

// 6. UnaryOperator<T> — Function<T, T> 의 특수
UnaryOperator<String> toUpper = String::toUpperCase;

// 7. BinaryOperator<T> — BiFunction<T, T, T>
BinaryOperator<Integer> max = Integer::max;

6.6 사용자 정의 함수형 인터페이스

// 도메인 함수형 인터페이스
@FunctionalInterface
public interface FareCalculator {
    BigDecimal calculate(int weight, int distance);
}

@FunctionalInterface
public interface ShipmentValidator {
    boolean validate(Shipment s);
}

// 사용
FareCalculator basicFare = (w, d) -> new BigDecimal("0.5").multiply(BigDecimal.valueOf(w));
FareCalculator premiumFare = (w, d) -> 
    new BigDecimal("1.0").multiply(BigDecimal.valueOf(w))
        .multiply(BigDecimal.valueOf(1 + d / 1000.0));

BigDecimal basicCost = basicFare.calculate(1000, 5000);
BigDecimal premiumCost = premiumFare.calculate(1000, 5000);

6.7 람다와 인터페이스의 관계

인스턴스화 불가의 우회:

인터페이스는 인스턴스화 불가.
하지만 람다로 "마치 인스턴스 같은" 객체 생성:

Runnable r = () -> { ... };
// 이는:
// Runnable r = new Runnable() {
//     @Override public void run() { ... }
// };

람다는 익명 클래스의 syntactic sugar 와 비슷.
실제로는 invokedynamic 명령어로 더 효율적.

6.8 메서드 참조 (Method Reference)

// 4가지 메서드 참조

// 1. static 메서드 참조
Function<String, Integer> f1 = Integer::parseInt;
// 동등: s -> Integer.parseInt(s)

// 2. 특정 인스턴스의 메서드 참조
String prefix = "Hello, ";
Function<String, String> f2 = prefix::concat;
// 동등: s -> prefix.concat(s)

// 3. 임의 객체의 메서드 참조
Function<String, Integer> f3 = String::length;
// 동등: s -> s.length()

// 4. 생성자 참조
Supplier<List<String>> f4 = ArrayList::new;
// 동등: () -> new ArrayList<>()

Function<Integer, List<String>> f5 = ArrayList::new;
// 동등: capacity -> new ArrayList<>(capacity)

6.9 Stream 과의 결합

List<Shipment> shipments = ...;

// 함수형 인터페이스 활용
List<String> activeBlNos = shipments.stream()
    .filter(s -> s.getStatus() == ShipmentStatus.ACTIVE)   // Predicate
    .map(Shipment::getBlNo)                                  // Function
    .collect(Collectors.toList());

shipments.forEach(System.out::println);   // Consumer

Optional<BigDecimal> maxWeight = shipments.stream()
    .map(Shipment::getWeight)               // Function
    .max(Comparator.naturalOrder());        // Comparator

BigDecimal total = shipments.stream()
    .map(Shipment::getFare)
    .reduce(BigDecimal.ZERO, BigDecimal::add);   // BinaryOperator

6.10 자기 점검 답변

함수형 인터페이스가 람다와 어떻게 연결되나?

:
1. 함수형 인터페이스: 추상 메서드가 정확히 1개
2. 람다: 그 1개 메서드의 구현을 간결하게 표현
3. 사용:

  • Runnable r = () -> {...} → run() 구현
  • Comparator<T> c = (a, b) -> ... → compare() 구현
  1. 추론:
    • 컴파일러가 매개변수 타입, 반환 타입 추론
    • 변수 선언에서 타입 정보 활용
  2. 메서드 참조: 람다의 더 간결한 형태

→ 함수형 프로그래밍의 핵심.


7️⃣ 자바 표준 인터페이스 활용

7.1 Comparable

public interface Comparable<T> {
    int compareTo(T other);
}

// 구현
public class Shipment implements Comparable<Shipment> {
    private LocalDate createdAt;
    
    @Override
    public int compareTo(Shipment other) {
        return this.createdAt.compareTo(other.createdAt);
    }
}

// 활용
List<Shipment> list = ...;
Collections.sort(list);   // Comparable 자동 사용
TreeSet<Shipment> set = new TreeSet<>(list);   // 자동 정렬

Comparable 의 의미:

  • "자기 자신을 비교 가능"
  • 자연 순서 (natural order)

7.2 Comparator

public interface Comparator<T> {
    int compare(T o1, T o2);
    
    // Java 8+ default + static 메서드들
    static <T extends Comparable<? super T>> Comparator<T> naturalOrder() { ... }
    default Comparator<T> reversed() { ... }
    default Comparator<T> thenComparing(Comparator<? super T> other) { ... }
    static <T, U extends Comparable<? super U>> 
        Comparator<T> comparing(Function<? super T, ? extends U> keyExtractor) { ... }
}

// 활용
List<Shipment> list = ...;

// 1. 람다
list.sort((s1, s2) -> s1.getWeight().compareTo(s2.getWeight()));

// 2. Comparator.comparing
list.sort(Comparator.comparing(Shipment::getWeight));

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

Comparator 의 의미:

  • "두 객체를 외부에서 비교"
  • 다양한 기준으로 비교 가능

7.3 Iterable

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

// 구현 예
public class MyCollection<T> implements Iterable<T> {
    private List<T> items = new ArrayList<>();
    
    @Override
    public Iterator<T> iterator() {
        return items.iterator();
    }
}

// for-each 자동 사용
MyCollection<String> collection = ...;
for (String s : collection) {   // Iterable 덕분에 가능
    System.out.println(s);
}

Iterable 의 의미:

  • "for-each 가능"
  • Collection 의 부모 인터페이스

7.4 Runnable, Callable

// Runnable — 결과 없는 작업
@FunctionalInterface
public interface Runnable {
    void run();
}

Runnable task = () -> {
    System.out.println("Running");
};

new Thread(task).start();

// Callable — 결과 있는 작업 (제네릭)
@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

Callable<String> task = () -> {
    Thread.sleep(1000);
    return "Done";
};

ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(task);
String result = future.get();

7.5 AutoCloseable

public interface AutoCloseable {
    void close() throws Exception;
}

// try-with-resources 와 함께
public class FileResource implements AutoCloseable {
    private final BufferedReader reader;
    
    public FileResource(String filename) throws IOException {
        this.reader = new BufferedReader(new FileReader(filename));
    }
    
    public String readLine() throws IOException {
        return reader.readLine();
    }
    
    @Override
    public void close() throws IOException {
        reader.close();
    }
}

// 사용 — close() 자동 호출
try (FileResource res = new FileResource("file.txt")) {
    String line = res.readLine();
} catch (IOException e) {
    // ...
}
// res.close() 자동 호출

7.6 CharSequence

public interface CharSequence {
    int length();
    char charAt(int index);
    CharSequence subSequence(int start, int end);
    String toString();
}

// 구현체:
// - String
// - StringBuilder
// - StringBuffer
// - CharBuffer

// 활용
public static void process(CharSequence cs) {
    for (int i = 0; i < cs.length(); i++) {
        System.out.println(cs.charAt(i));
    }
}

process("hello");                  // String
process(new StringBuilder("ab"));  // StringBuilder

7.7 자바 표준 인터페이스 종합

컬렉션:
  - Collection<E>
  - List<E>, Set<E>, Queue<E>
  - Map<K, V>
  - Iterator<E>, Iterable<E>
  - Comparator<T>

함수형:
  - Function<T, R>
  - Predicate<T>
  - Consumer<T>
  - Supplier<T>
  - Runnable, Callable<V>

비교:
  - Comparable<T>
  - Comparator<T>

I/O:
  - Closeable, AutoCloseable
  - Readable, Appendable

리소스:
  - Serializable (marker)
  - Cloneable (marker)
  - RandomAccess (marker)

기타:
  - CharSequence
  - Number
  - EventListener

7.8 ILIC 표준 활용 패턴

// 1. Comparable + Comparator
public class Shipment implements Comparable<Shipment> {
    @Override
    public int compareTo(Shipment other) {
        return this.getId().compareTo(other.getId());
    }
}

List<Shipment> list = ...;
Collections.sort(list);   // 자연 순서

// 다른 기준
list.sort(Comparator.comparing(Shipment::getCreatedAt));

// 2. Iterable + for-each
public class ShipmentBatch implements Iterable<Shipment> {
    private List<Shipment> shipments;
    
    @Override
    public Iterator<Shipment> iterator() {
        return shipments.iterator();
    }
}

for (Shipment s : batch) { ... }

// 3. Runnable + Thread
Runnable task = () -> processShipment(shipment);
new Thread(task).start();

// 4. AutoCloseable + try-with-resources
public class ShipmentLock implements AutoCloseable {
    private final ReentrantLock lock = new ReentrantLock();
    
    public ShipmentLock() {
        lock.lock();
    }
    
    @Override
    public void close() {
        lock.unlock();
    }
}

try (ShipmentLock l = new ShipmentLock()) {
    // 동기화 작업
}

// 5. 함수형 인터페이스
shipments.stream()
    .filter(Shipment::isActive)
    .map(Shipment::getBlNo)
    .forEach(System.out::println);

7.9 자기 점검 답변

가장 자주 쓰는 자바 표준 인터페이스 5가지는?

:
1. Collection / List / Map — 컬렉션의 표준
2. Comparable / Comparator — 정렬
3. Iterable / Iterator — 순회
4. Runnable / Callable — 동시성
5. 함수형 인터페이스 (Function, Predicate, Consumer, Supplier) — 람다

추가:

  • AutoCloseable — try-with-resources
  • Serializable — 직렬화 (marker)
  • CharSequence — 문자열 추상화

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


8️⃣ 인터페이스의 진화 — 역사

8.1 Java 1.0 ~ 7 — 순수 인터페이스

초기 자바 (1996 ~ 2011):

인터페이스 = 메서드 시그니처 + 상수

규칙:
  - 모든 메서드 추상
  - 모든 필드 public static final
  - 생성자 X
  - 구현 X
  - 다중 구현 가능

8.2 Java 5 — 어노테이션과 제네릭

// Java 5+
@FunctionalInterface   // 마커 인터페이스 → 어노테이션
public interface Comparable<T> {   // 제네릭
    int compareTo(T other);
}

영향:

  • 마커 인터페이스의 일부 대체 (어노테이션)
  • 타입 안전한 인터페이스 (제네릭)

8.3 Java 8 — 혁명적 변화

public interface Comparator<T> {
    int compare(T o1, T o2);   // 추상
    
    // ★ Java 8 추가: default 메서드
    default Comparator<T> reversed() {
        return (a, b) -> compare(b, a);
    }
    
    // ★ Java 8 추가: static 메서드
    static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {
        return (a, b) -> a.compareTo(b);
    }
}

Java 8 의 변화:

  • default 메서드 — 인터페이스에 구현 가능
  • static 메서드 — 유틸리티 함수
  • 함수형 인터페이스 — 람다와 연결
  • 다음 Unit 4.3 에서 상세

8.4 Java 9 — private 메서드

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

Java 9 의 변화:

  • private 메서드 — default 메서드 간 코드 공유
  • private static 메서드 — static 메서드 간 공유

8.5 진화 요약

시간순 변화:

Java 1.0 (1996):
  - 인터페이스 도입
  - 추상 메서드 + 상수만

Java 1.2 (1998):
  - Collection 프레임워크
  - List, Set, Map 인터페이스 표준화

Java 5 (2004):
  - 제네릭 (Generic)
  - 어노테이션
  - Enhanced for-each (Iterable)

Java 8 (2014) ★:
  - default 메서드
  - static 메서드
  - 함수형 인터페이스
  - 람다 + 메서드 참조
  - Stream API

Java 9 (2017):
  - private 메서드
  - private static 메서드

Java 21 (2023):
  - Sealed 인터페이스 (확장 제한)
  - Pattern matching for switch

8.6 Java 8 의 동기

왜 default 메서드가 등장?

문제:
  - 기존 인터페이스 (Collection 등) 에 새 메서드 추가 불가
  - 추가하면 모든 구현체가 깨짐
  - 호환성 문제

해결:
  - default 메서드 = 기본 구현 제공
  - 기존 구현체는 그대로 사용
  - 새 메서드도 자동 사용 가능

예:
  Collection.forEach() — Java 8 추가
  Collection.removeIf() — Java 8 추가
  Collection.stream() — Java 8 추가
  → 기존 ArrayList 코드 그대로 동작

8.7 Sealed 인터페이스 (Java 17+)

// Sealed: 확장을 명시적으로 제한
public sealed interface Shape 
    permits Circle, Rectangle, Triangle {
    
    double area();
}

public final class Circle implements Shape {
    private final double radius;
    
    @Override
    public double area() {
        return Math.PI * radius * radius;
    }
}

// Triangle, Rectangle 도 비슷

// Switch + pattern matching
public double describeShape(Shape s) {
    return switch (s) {
        case Circle c -> "Circle with area " + c.area();
        case Rectangle r -> "Rectangle with area " + r.area();
        case Triangle t -> "Triangle with area " + t.area();
        // default 불필요 — sealed 라 완전
    };
}

특징:

  • 확장 가능한 클래스 명시
  • 컴파일 타임 완전성 검사
  • pattern matching 과 결합

8.8 자기 점검 답변

Java 8 의 default 메서드가 등장한 이유는?

:
1. 하위 호환성:

  • 기존 인터페이스 (Collection 등) 에 새 메서드 추가 필요
  • 추가하면 모든 구현체가 컴파일 에러
  • 기본 구현 제공으로 해결
  1. 함수형 프로그래밍 지원:

    • Stream API 추가
    • Collection.stream() 등 메서드 필요
    • default 로 모든 컬렉션에 자동 제공
  2. 인터페이스의 진화:

    • 능력 추가 가능
    • Comparator 의 reversed(), thenComparing 등
    • 더 풍부한 API

→ Java 8 이 자바의 가장 큰 진화.


9️⃣ 면접 + 자기 점검

9.1 면접 단골 질문 매핑

Q핵심 답변
인터페이스의 의미?"can-do" 능력 표현
메서드 자동 수식자?public abstract
필드 자동 수식자?public static final
다중 구현이 가능한 이유?구현 없으니 모호함 X
생성자가 없는 이유?인스턴스화 불가 + 상태 없음
인스턴스 필드 불가 이유?다중 구현 모호함 회피
마커 인터페이스의 예?Serializable, RandomAccess
어노테이션과 마커 차이?타입 시스템 vs 메타데이터
함수형 인터페이스?추상 메서드 1개
@FunctionalInterface?검증 어노테이션
Comparable vs Comparator?자기 비교 vs 외부 비교
Iterable 의 의미?for-each 가능
람다와 인터페이스 관계?함수형 인터페이스의 단순화
Java 8 default 메서드 이유?하위 호환성

9.2 자기 점검 체크리스트

기본 이해

  • interface 키워드의 의미를 안다
  • 자동 수식자 (public abstract, public static final) 를 안다
  • 다중 구현 가능 이유를 안다
  • 생성자 없음 이유 4가지를 안다
  • 인스턴스 필드 불가 이유를 안다

마커와 함수형

  • 마커 인터페이스의 정의를 안다
  • Serializable, RandomAccess 의 활용을 안다
  • 어노테이션과 마커의 차이를 안다
  • 함수형 인터페이스의 정의를 안다
  • 람다와의 연결을 안다

자바 표준 인터페이스

  • Comparable, Comparator 의 차이를 안다
  • Iterable, Iterator 의 역할을 안다
  • Runnable, Callable 의 차이를 안다
  • AutoCloseable + try-with-resources
  • 함수형 인터페이스 5가지 (Function 등) 를 안다

진화

  • Java 1.0 ~ 8 ~ 9 의 진화를 안다
  • default 메서드의 등장 이유를 안다
  • static 메서드 추가의 의미를 안다
  • private 메서드 (Java 9) 의 활용을 안다
  • Sealed 인터페이스 (Java 17+) 를 안다

9.3 추가 심화 질문

Q1: 인터페이스에 상수가 많으면 안 좋은가?

답:

  • 상수 자체는 OK
  • 단, 상수 인터페이스 안티패턴 회피
  • implements 만으로 상수 노출은 캡슐화 위반
  • 대안: final class + private 생성자 + public static final
  • 또는 static import

Q2: 인터페이스를 인스턴스화 할 수 있나?

답:

  • 직접: ❌ 불가
  • 간접:
    • 익명 클래스: Runnable r = new Runnable() {...}
    • 람다: Runnable r = () -> {...}
    • 메서드 참조: Runnable r = obj::method
    • 구현 클래스: Runnable r = new MyRunnable()

Q3: 인터페이스끼리 extends 가 가능한 이유는?

답:

  • 인터페이스는 메서드 명세만
  • 다중 상속도 가능 (Diamond Problem 없음)
  • interface A extends B, C OK
  • 구현 없으니 충돌 없음
  • Java 8+ default 메서드 등장으로 일부 충돌 가능 (다음 Unit)

Q4: 인터페이스에 main 메서드를 둘 수 있나?

답:

  • Java 8+ 가능 (static 메서드)
  • 인터페이스도 클래스의 일종
  • public static 자동
public interface MyInterface {
    static void main(String[] args) {
        System.out.println("Hello");
    }
}

Q5: 인터페이스를 final 로 만들 수 있나?

답:

  • ❌ 불가 (final interface 컴파일 에러)
  • 이유:
    • 인터페이스는 본질적으로 확장 (implements)
    • final 은 확장 금지
    • 모순
  • Java 17+ 의 sealed interface 가 비슷한 의도 (제한적 확장)

🎯 핵심 요약 — 3줄 정리

1. 인터페이스 = "can-do" 능력

  • 메서드는 public abstract (자동)
  • 필드는 public static final (자동)
  • 생성자 X, 인스턴스 필드 X

2. 다중 구현의 자유

  • 클래스 단일 상속의 한계 보완
  • "is-a 단일 + can-do 다중"
  • 모호함 없음 (구현 없으니)

3. 진화의 역사

  • Java 1.0: 순수 명세
  • Java 5: 어노테이션, 제네릭
  • Java 8: default/static 메서드 (혁명), 람다
  • Java 9: private 메서드
  • Java 17+: sealed 인터페이스

📚 다음으로...

Unit 4.3 — Java 8 default & static 메서드

이번 Unit에서 인터페이스의 본질을 봤다면, 다음은 Java 8 의 혁명.

  • default 메서드의 등장 배경
  • static 메서드의 활용
  • diamond problem 의 부활과 해결
  • private 메서드 (Java 9)
  • Stream/Optional 과의 결합

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 — 추상화의 두 도구 (2/4 진행)

총: 15/43 Unit 작성 (약 35%)
profile
Software Developer

0개의 댓글