자바 백엔드 입문 3편 — 인터페이스와 다형성

2026-05-16자바 백엔드 입문

자바 백엔드 입문 3편. 인터페이스·구현체·다형성·추상 클래스 비교를 USB 포트 비유로 풀고, 왜 이게 Spring DI(의존성 주입)의 토대가 되는지까지 한 흐름으로 풀어쓴 학습 노트.

📚 자바 백엔드 입문 · 3편 — 인터페이스와 다형성

이 글은 자바 백엔드 입문 시리즈 59편 중 3편이에요. 2편에서 클래스로 객체를 찍어낸다는 그림을 잡았다면, 3편은 한 단계 더 추상적인 개념 — 인터페이스와 다형성을 잡습니다. Spring을 본격적으로 다루는 5편부터 "왜 Spring이 구현체 대신 인터페이스를 주입하는가" 가 자주 등장하는데, 그 의문이 풀리는 글이에요.

인터페이스가 어렵게 들리는 이유

처음 "인터페이스" 라는 단어를 들으면 머리에 그림이 안 떠올라요. 두 가지가 원인이에요.

첫째, 단어 자체가 추상적이거든요. 클래스는 "붕어빵 틀" 처럼 비유가 쉬운데, 인터페이스는 "규격" 이라는 단어가 와 닿지 않아요.

둘째, "어차피 클래스로 만들면 되는데 왜 굳이 인터페이스를?" 가 안 보입니다. 이 질문은 "교체 가능성" 이라는 한 단어로 풀려요 — 같은 규격을 따른다면 안에 들어가는 실물이 무엇이든 바꿔 끼울 수 있다는 뉘앙스.

이 글에서는 인터페이스 = USB 포트 규격, 구현체 = USB 포트에 꽂히는 장치 비유로 풀어 갑니다. 끝까지 따라오시면 "Spring이 왜 인터페이스를 그렇게 좋아하는지" 가 한 번에 박혀요.

인터페이스 = USB 포트 규격

인터페이스(Interface)"이런 메서드들을 가져야 한다"계약서예요. 안의 구현은 비어 있고, 메서드 이름·매개변수·반환 타입만 적혀 있어요.

public interface Weapon {
    void attack();
    int getDamage();
}

이게 인터페이스예요. "이 인터페이스를 따르는 무기는 attack()getDamage() 를 반드시 가져야 한다" 는 규격만 정의했고, "실제로 어떻게 공격하는지" 는 비어 있어요.

USB 포트 비유로 풀어볼게요. USB 포트는 인터페이스예요. "이런 모양의 단자와 이런 핀 배열을 갖는다" 는 규격만 정의했지, "이 포트에 무엇을 꽂으면 무엇이 일어난다" 는 안 정해져 있어요. 마우스를 꽂으면 마우스 동작, 키보드를 꽂으면 키보드 동작, USB 메모리를 꽂으면 저장 매체 동작 — 꽂히는 장치에 따라 결과가 달라지는데, USB 포트 자체는 그걸 모릅니다.

구현체 = USB 포트에 꽂히는 실제 장치들

인터페이스를 따르는 클래스를 구현체(Implementation) 라고 불러요. implements 키워드로 "나 이 인터페이스 따른다" 고 선언해요.

public class Sword implements Weapon {
    @Override
    public void attack() {
        System.out.println("칼을 휘두른다");
    }
    @Override
    public int getDamage() {
        return 20;
    }
}

public class Bow implements Weapon {
    @Override
    public void attack() {
        System.out.println("화살을 쏜다");
    }
    @Override
    public int getDamage() {
        return 15;
    }
}

SwordBow도 둘 다 Weapon 인터페이스를 따라요. 둘 다 attack()getDamage() 를 가지지만, 안의 동작은 완전히 달라요. USB 포트로 치면 "마우스도 키보드도 같은 USB 단자에 꽂히지만, 안에서 하는 일은 완전히 다른" 그림이에요.

여기서 시험 함정이 하나 있어요. "인터페이스는 메서드 본문을 가질 수 없다" 가 자바 7까지의 룰이었어요. 자바 8부터는 default 메서드라는 게 추가되어 인터페이스 안에도 메서드 본문을 박을 수 있게 됐어요. 시험·면접에서 "인터페이스에 절대 메서드 본문이 없다" 가 보기로 나오면 X — 자바 8 이후 OK라는 게 정답이에요.

한 줄 정리 — 인터페이스 = USB 포트 규격, 구현체(implements) = 그 규격을 따르는 실제 장치. implements 키워드로 "나 이 인터페이스 따른다" 선언.

다형성 — 한 변수에 마우스도 키보드도 들어간다

여기서 자바 객체지향의 핵심 개념 하나가 등장해요. 다형성(Polymorphism). "하나의 변수가 여러 다른 객체를 담을 수 있다" 는 능력이에요.

Weapon w;       // 인터페이스 타입 변수

w = new Sword();
w.attack();     // "칼을 휘두른다"

w = new Bow();
w.attack();     // "화살을 쏜다"

w 의 타입은 Weapon (인터페이스)인데, 실제로 들어가는 객체는 Sword 일 수도 Bow 일 수도 있어요. "USB 포트 변수에 마우스를 꽂든 키보드를 꽂든 둘 다 OK" 인 셈이에요. 변수 자체는 어느 무기가 들어 있는지 신경 안 써요 — 그저 Weapon 인터페이스가 약속한 attack() 만 호출할 수 있으면 충분.

다형성의 진짜 힘은 함수 매개변수에서 빛나요.

public void useWeapon(Weapon w) {   // 인터페이스를 매개변수로
    w.attack();
}

useWeapon(new Sword());   // OK
useWeapon(new Bow());     // OK
useWeapon(new Hammer());  // 새로운 무기를 추가해도 OK — useWeapon 코드 안 고침

새로운 무기를 만들 때 useWeapon 함수를 한 줄도 안 고쳐도 돼요. 인터페이스만 따르면 자동으로 끼워서 동작합니다. 이 "기존 코드 안 건드리고 새 구현체 끼워 넣는" 능력이 객체지향의 가장 큰 무기예요.

💡 객체지향 5원칙 OCP

Open-Closed Principle"확장에는 열려 있고 수정에는 닫혀 있다". 새 기능을 추가할 때 기존 코드 수정 없이 새 구현체만 만들면 끝나는 설계 원칙이에요. 다형성이 이 원칙을 가능하게 합니다.

인터페이스 vs 추상 클래스 — 비슷한데 다른 둘

자바에는 인터페이스와 비슷한 추상 클래스(Abstract Class) 라는 게 또 있어요. 둘 다 "미완성 설계도" 라는 점은 같은데, 결정적으로 다른 부분이 있어요.

구분인터페이스추상 클래스
키워드interface + implementsabstract class + extends
다중 상속여러 개 동시 implements OK한 개만 extends 가능
필드상수(public static final)만일반 필드 가능
메서드 본문자바 8+ default 메서드만일반 메서드 본문 가능
비유"규격" — 무엇을 할 수 있는가"미완성 부모 클래스" — 공통 코드 + 일부 빈 구멍

여기서 시험 함정 정말 자주 나와요. "인터페이스는 다중 implements 가능, 클래스 상속은 단일 extends만 가능". 자바가 다중 상속을 막은 이유는 "다이아몬드 문제" — 두 부모 클래스에 같은 메서드가 있으면 어느 걸 따라야 하는가의 충돌 — 때문이에요. 인터페이스는 메서드 본문이 비어 있어서(주로) 이 문제가 없어 다중 implements가 허용됩니다.

언제 인터페이스 vs 언제 추상 클래스? 단순한 룰 한 줄 — 공통 코드를 공유해야 하면 추상 클래스, "이걸 할 수 있다" 만 정의하면 인터페이스. 90% 케이스에서는 인터페이스가 더 자주 쓰여요.

왜 인터페이스가 Spring DI의 토대인가

5편부터 본격적으로 다룰 Spring의 의존성 주입(DI, Dependency Injection) 미리보기 한 줄. Spring이 가장 잘하는 일은 "인터페이스 변수에 적절한 구현체를 자동으로 끼워 넣어주는" 거예요.

@Service
public class OrderService {
    private final PaymentGateway paymentGateway;   // 인터페이스 타입

    public OrderService(PaymentGateway paymentGateway) {
        this.paymentGateway = paymentGateway;
    }

    public void placeOrder() {
        paymentGateway.pay(10000);
    }
}

PaymentGateway 는 인터페이스예요. 구현체는 KakaoPayGateway 일 수도 TossPayGateway 일 수도 있어요. Spring이 시작 시점에 "PaymentGateway 자리에 어떤 구현체를 끼워 넣을지" 알아서 정해서 주입해줍니다. 그래서 OrderService"카카오페이를 쓸지 토스를 쓸지" 신경 안 써요. 그냥 "PaymentGateway 라는 인터페이스 따르는 무언가가 들어 있구나" 만 알면 됩니다.

이게 다형성의 실전 활용이에요. 인터페이스가 박혀 있어야 "실제 결제 게이트웨이를 카카오페이에서 토스로 바꿔도 OrderService 한 줄도 안 고쳐도 되는" 마법이 가능해집니다.

한 줄 정리 — 인터페이스 + 다형성 + DI = "기존 코드 안 건드리고 구현체만 바꿔 끼우는" Spring의 핵심 무기.

시험 직전 한 번 더 — 인터페이스·다형성 입문자가 매번 헷갈리는 것

  • 인터페이스 = USB 포트 규격, 구현체 = 그 포트에 꽂히는 실제 장치
  • interface 키워드로 선언, implements 키워드로 구현
  • 한 클래스가 여러 인터페이스 동시 구현 가능 (다중 implements)
  • 클래스 상속은 단일 extends만 가능 (다이아몬드 문제 회피)
  • 인터페이스 안의 메서드는 자동으로 public abstract (생략 가능)
  • 인터페이스 안의 필드는 자동으로 public static final (상수만 가능)
  • 자바 8부터 인터페이스에 default 메서드 본문 박을 수 있음
  • "인터페이스는 무조건 메서드 본문 없다" 는 자바 7까지 룰 — 자바 8 이후 X
  • 다형성(Polymorphism) = 하나의 변수가 여러 다른 객체를 담을 수 있는 능력
  • 다형성의 핵심 활용 = 함수 매개변수에 인터페이스 박기
  • OCP(Open-Closed Principle) = 새 구현체 추가 시 기존 코드 안 고치는 설계
  • 추상 클래스(abstract class) = 인터페이스와 비슷하지만 일반 메서드·필드 가능
  • 추상 클래스는 단일 상속만 가능, 인터페이스는 다중 구현 가능
  • 공통 코드 공유 = 추상 클래스, "무엇을 할 수 있다" 만 = 인터페이스
  • 90% 케이스에서 추상 클래스보다 인터페이스가 더 자주 쓰임
  • @Override 어노테이션 = "이 메서드는 부모 인터페이스/클래스의 메서드를 구현 중" 표시
  • @Override 박으면 컴파일러가 메서드 시그니처 매칭 검증해줘 오타 방지
  • Spring DI = 인터페이스 타입 필드에 자동으로 구현체 주입
  • 인터페이스 박는 진짜 이유 = "구현체 교체 가능성". 한 마디로 "갈아 끼울 수 있게"
  • @Service·@Component·@Repository 모두 Spring이 "이 클래스로 객체 찍어내라" 표시

시리즈 다른 편 (앞뒤 글 모음)

이전 글:

다음 글:

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!