CodeGym /행동 /JAVA 25 SELF /인터페이스의 default 메서드

인터페이스의 default 메서드

JAVA 25 SELF
레벨 21 , 레슨 2
사용 가능

1. 소개

한때(Java 8 이전) 인터페이스는 매우 엄격했습니다: 그 안에는 구현이 없는 추상 메서드와 상수(public static final)만 선언할 수 있었습니다. 이는 편리했지만, 한 가지 큰 문제가 생겼습니다: 라이브러리의 진화.

상황을 상상해 봅시다

여러분은 다음과 같은 인터페이스가 있는 인기 라이브러리를 개발했습니다:

public interface Movable {
    void move(int x, int y);
}

전 세계 수천 명의 개발자가 이 인터페이스를 구현하는 자신의 클래스를 작성합니다. 몇 년이 지나, 모두에게 객체를 원래 위치로 되돌리는 reset() 메서드가 필요하다는 것을 깨닫습니다. 그래서 인터페이스에 다음을 추가합니다:

public interface Movable {
    void move(int x, int y);
    void reset();
}

그리고 아수라장이 시작됩니다: 해당 인터페이스를 사용하는 모든 프로젝트가 컴파일되지 않습니다! 이제 새 메서드를 구현해야 하는데, 아무도 그것을 몰랐기 때문이죠. 마이그레이션은 고통으로 변합니다.

default 메서드 — 해법!

Java 8은 default 메서드를 도입했습니다: 이제 인터페이스 안에 구현이 있는 메서드를 추가할 수 있습니다! 기존 클래스들은 자동으로 기본 구현을 받기 때문에 코드가 깨지지 않습니다. 원한다면 메서드를 자신의 방식으로 재정의할 수도 있습니다.

2. default 메서드 문법

default 메서드는 인터페이스 내부에 구현을 가진 일반 메서드이며, 키워드 default로 표시됩니다.

public interface Movable {
    void move(int x, int y);

    default void reset() {
        // 전형적인 구현: 좌표 원점으로 돌아가기
        move(0, 0);
    }
}

설명:

  • 인터페이스의 모든 메서드는 기본적으로 publicabstract이지만, default 메서드는 추상이 아니라 본문을 가집니다.
  • 키워드 default는 항상 메서드의 반환 타입 앞에 위치합니다.

클래스에서는 어떻게 보일까요?

public class Robot implements Movable {
    private int x, y;

    @Override
    public void move(int x, int y) {
        this.x = x;
        this.y = y;
        System.out.println("로봇이 (" + x + ", " + y + ")로 이동했습니다");
    }

    // reset()을 구현할 필요는 없습니다 — default 버전이 동작합니다!
}

이제 객체 Robot에서 reset()을 호출하면, Movable 인터페이스의 구현이 실행됩니다:

public class Main {
    public static void main(String[] args) {
        Movable robot = new Robot();
        robot.move(10, 20); // 로봇이 (10, 20)로 이동했습니다
        robot.reset();      // 로봇이 (0, 0)로 이동했습니다
    }
}

3. 표준 라이브러리의 default 메서드

default 메서드는 방대한 표준 인터페이스들을 기존 코드를 깨뜨리지 않고 발전시키기 위해 도입되었습니다.

예: 인터페이스 List (Java 8+)

Java 8에서는 인터페이스 List에 구현이 포함된 메서드들, 예를 들어 forEach, replaceAll, sort가 추가되었습니다:

default void forEach(Consumer<Entity> action) {
    for (Entity e : this) {
        action.accept(e);
    }
}

직접 리스트를 구현하고 forEach를 재정의하지 않았더라도, default 메서드 덕분에 여전히 동작합니다.

제네릭 타입(Consumer<Entity>)에 대해서는 26레벨에서 더 배우게 됩니다 :P

4. 왜 default 메서드가 필요한가?

  • 코드를 깨뜨리지 않고 API를 발전: 인터페이스에 새 메서드를 추가하더라도 기존 모든 클래스에서 이를 구현할 필요가 없습니다.
  • 보편적인 동작 템플릿: 기본 동작을 선언해 두고, 클래스가 이를 그대로 사용하거나 재정의할 수 있습니다.
  • 중복 감소: 대부분의 구현에서 동일한 동작이라면 — 각 클래스에 코드를 복사할 필요가 없습니다.

비유

집 임대 계약서(인터페이스)가 있다고 상상해 보세요. 예전에는 “임차인은 수도 요금을 지불해야 한다”라고만 적혀 있었습니다. 그 후 “임차인은 전기 요금을 지불해야 한다”가 추가되었습니다. default 메서드가 없다면, 모든 임차인과의 계약서를 전부 다시 써야 했을 것입니다! 하지만 default 메서드가 있다면 — 조항을 하나 추가하기만 하면 되고, 필요하다면 각자 따로 합의(재정의)하면 됩니다.

5. default 메서드의 제약과 특징

default 메서드는 클래스 Object의 메서드를 재정의할 수 없다

인터페이스에서 equals, hashCode, toStringObject 클래스의 시그니처가 같은 default 메서드를 선언할 수 없습니다. 이는 혼란을 방지하기 위한 것입니다. 모든 Java 객체는 이미 이러한 메서드를 가지고 있기 때문입니다.

// 컴파일 오류!
interface Broken {
    default boolean equals(Object obj) { return false; }
}

default 메서드 간 충돌

만약 어떤 클래스가 두 인터페이스를 구현하고, 각 인터페이스에 시그니처가 같은 default 메서드가 있다면 어떻게 될까요? Java 컴파일러는 솔직하게 말할 것입니다: “직접 결정해. 나는 무엇을 해야 할지 모르겠어!”

interface A {
    default void hello() { System.out.println("Hello from A"); }
}

interface B {
    default void hello() { System.out.println("Hello from B"); }
}

class C implements A, B {
    // 반드시 충돌을 해결해야 합니다:
    @Override
    public void hello() {
        // 어느 쪽 메서드를 호출할지 선택하거나 직접 구현할 수 있습니다
        A.super.hello(); // 또는 B.super.hello();
    }
}

클래스 C에서 hello()를 구현하지 않으면 컴파일 오류가 발생합니다.

default 메서드는 인터페이스의 다른 메서드를 호출할 수 있다

default 메서드는 인터페이스의 다른 메서드(추상 메서드 포함)를 호출할 수 있습니다. 단, 클래스에 해당 메서드의 구현이 있어야 합니다.

interface Printer {
    void print(String text);

    default void printTwice(String text) {
        print(text);
        print(text);
    }
}

6. 예제: default 메서드로 애플리케이션 발전시키기

인터페이스 Movable에서 default 메서드를 사용하는 예를 보겠습니다:

public interface Movable {
    void move(int x, int y);

    default void reset() {
        move(0, 0);
    }
}

Robot 클래스가 이 인터페이스를 구현합니다:

public class Robot implements Movable {
    private int x = 5;
    private int y = 7;

    @Override
    public void move(int x, int y) {
        this.x = x;
        this.y = y;
        System.out.println("로봇이 (" + x + ", " + y + ")로 이동했습니다");
    }

    // reset()은 구현하지 않고 — default 메서드를 사용합니다!
}

이제 두 메서드를 모두 호출해 봅시다:

public class Main {
    public static void main(String[] args) {
        Movable robot = new Robot();
        robot.move(10, 20); // 로봇이 (10, 20)로 이동했습니다
        robot.reset();      // 로봇이 (0, 0)로 이동했습니다
    }
}

만약 Robot이 어떤 특별한 방식으로 초기화되길 원한다면 — 클래스에서 reset()만 재정의하면 됩니다:

@Override
public void reset() {
    System.out.println("로봇이 전원을 끄고 기지로 돌아갑니다!");
    move(0, 0);
}

7. default 메서드와 인터페이스 다중 구현

default 메서드는 클래스가 여러 인터페이스를 구현할 때 특히 유용합니다. 하지만 주의할 점이 있습니다: 두 인터페이스에 시그니처가 같은 default 메서드가 있다면, 컴파일러는 충돌을 명시적으로 해결하라고 요구합니다.

충돌 예시

interface A {
    default void show() { System.out.println("A"); }
}
interface B {
    default void show() { System.out.println("B"); }
}
class C implements A, B {
    @Override
    public void show() {
        // 어느 default 메서드를 사용할지 명시적으로 선택
        A.super.show(); // 또는 B.super.show();
    }
}

8. 다이어그램: default 메서드 호출은 어떻게 동작하는가


+-------------------+
|   Movable         |
|-------------------|
| +move(int, int)   | <- 추상 메서드
| +reset()          | <- default 메서드
+-------------------+
         ^
         |
+-------------------+
|   Robot           |
|-------------------|
| +move(int, int)   | <- 구현함
|                   | (reset()은 구현하지 않음)
+-------------------+
         |
     reset() 호출
         |
   인터페이스의 구현
   Movable에서 제공
default 메서드 호출: 인터페이스의 기본 구현이 사용됨

9. default 메서드 작업 시 흔한 실수

오류 1: 구현 없이 default 메서드를 만들려는 시도.
default 메서드는 반드시 본문을 가져야 합니다! 만약 default void foo();라고 쓰면, 컴파일러는 곧바로 이렇게 말할 겁니다: “중괄호를 잊으셨나요?”

오류 2: 서로 다른 인터페이스의 default 메서드 충돌.
클래스가 시그니처가 같은 default 메서드를 가진 두 인터페이스를 구현하면, 반드시 충돌을 명시적으로 해결해야 합니다 — 그렇지 않으면 컴파일러가 코드를 컴파일하지 않습니다.

오류 3: Object의 메서드 시그니처로 default 메서드를 선언하려는 시도.
인터페이스에서 equals, hashCode, toString을 default 메서드로 만들 수 없습니다 — 이런 이름은 오직 추상 메서드로만 선언할 수 있습니다.

오류 4: default 메서드는 “마법”이 아니라 단지 편리한 수단이라는 점을 잊는다.
default 메서드는 인터페이스가 계약이라는 원칙을 무효화하지 않습니다. 기본 동작이 맞지 않으면 — 클래스에서 언제든 default 메서드를 재정의하세요.

1
과제
JAVA 25 SELF, 레벨 21, 레슨 2
잠금
스마트 기기 리모컨 ⚙️
스마트 기기 리모컨 ⚙️
1
과제
JAVA 25 SELF, 레벨 21, 레슨 2
잠금
동물원에서 다양한 동물들의 행동 🐕
동물원에서 다양한 동물들의 행동 🐕
1
과제
JAVA 25 SELF, 레벨 21, 레슨 2
잠금
이중 알림 시스템 🔔🔔
이중 알림 시스템 🔔🔔
1
과제
JAVA 25 SELF, 레벨 21, 레슨 2
잠금
다른 시스템 모듈 통합 🧩
다른 시스템 모듈 통합 🧩
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION