본문 바로가기
카테고리 없음

Java 인터페이스 완전 이해하기

by silvertogold100 2025. 8. 12.
반응형

🎁 인터페이스란 무엇일까?

인터페이스를 쉽게 이해하기 위해 일상 생활의 비유를 들어보겠습니다.

아파트 입주자 계약서 같은 인터페이스

아파트에 입주할 때 계약서를 작성하잖아요? 그 계약서에는 "입주자라면 이런 규칙들을 지켜야 하고, 이런 혜택들을 누릴 수 있습니다"라고 명시되어 있습니다.

인터페이스도 마찬가지입니다!

  • "우리 인터페이스를 구현하시면 수영장, 헬스장, 조식뷔폐 등의 여러가지 기능들을 제공드립니다"
  • "대신 이 메소드들은 반드시 구현해주셔야 합니다!"

포레스트 검프의 초콜릿 상자 비유

포레스트 검프에서 "Life is like a box of chocolates. You never know what you're gonna get"이라는 명대사가 있죠?

인터페이스도 마찬가지입니다. 겉으로 보기엔 같은 초콜릿 상자(인터페이스)지만, 안에 들어있는 초콜릿(구현체)은 모두 다를 수 있어요.

📋 인터페이스의 정의

인터페이스는 다양한 객체들이 공통적으로 가져야 할 기능들을 규격(메소드 정의)으로 정의해 놓은 설계도 혹은 규약입니다.

이를 통해:

  • 객체 간의 느슨한 결합(Loose Coupling)을 가능하게 함
  • 다형성을 활용하여 코드를 더 유연하고 확장성 있게 만듦
  • 구현할 기능에 대한 일반화를 제공

🔧 인터페이스 구현 방법

기본 구현 문법

public class 구현클래스명 implements interfaceA, interfaceB, interfaceC {
    // 인터페이스의 추상 메소드들을 반드시 구현해야 함
}

상속과 인터페이스 구현을 동시에

public class Bird extends Animal implements 날다, 먹다, 소리내다 {
    // Animal 클래스 상속 + 3개 인터페이스 구현
}

위 예제에서 Bird 클래스는:

  • Animal 클래스의 필드와 기능을 상속받을 수 있음
  • 날고, 먹고, 소리내는 기능을 사용할 수 있음
  • 중요: 각 인터페이스의 메소드들을 Bird에 맞도록 반드시 구체적으로 구현해야 함

🏗️ 인터페이스의 구성 요소

1. 상수 필드

public interface MyInterface {
    public static final int MAX_VALUE = 100;
    // 또는 간단히
    int MIN_VALUE = 0; // public static final이 자동 추가됨
}

2. 추상 메소드

public interface Drawable {
    public void draw(); // public abstract가 자동 추가됨
    void move(int x, int y);
}

3. 디폴트 메소드 (Java 8+)

public interface Calculator {
    // 추상 메소드
    int add(int a, int b);
    
    // 디폴트 메소드 - 구현체에서 선택적으로 오버라이드 가능
    default int multiply(int a, int b) {
        return a * b;
    }
}

4. static 메소드 (Java 8+)

public interface MathUtils {
    static int max(int a, int b) {
        return a > b ? a : b;
    }
}

5. private 메소드 (Java 9+)

public interface Logger {
    default void logInfo(String message) {
        log(message, "INFO");
    }
    
    default void logError(String message) {
        log(message, "ERROR");
    }
    
    // private 메소드 - 인터페이스 내부에서만 사용
    private void log(String message, String level) {
        System.out.println(level + ": " + message);
    }
}

🔗 인터페이스 상속

인터페이스 간 다중 상속

interface A {
    void methodA();
}

interface B {
    void methodB();
}

interface C extends A, B {  // 다중 상속 가능
    void methodC();
}

public class MyClass implements C {
    // methodA, methodB, methodC 모두 구현해야 함
    @Override
    public void methodA() { /* 구현 */ }
    
    @Override
    public void methodB() { /* 구현 */ }
    
    @Override
    public void methodC() { /* 구현 */ }
}

🔄 인터페이스 타입 변환

자동 타입 변환

// 인터페이스 참조변수에 구현 객체 대입 시 자동 변환
RemoteControl rc = new SmartTelevision();
// SmartTelevision 객체가 RemoteControl 타입으로 자동 변환

상속 관계에서의 자동 변환

class SmartTelevision implements RemoteControl { }
class UltraSmartTV extends SmartTelevision { }

RemoteControl rc = new UltraSmartTV(); // 자동 변환 가능

강제 타입 변환 (캐스팅)

RemoteControl rc = new SmartTelevision();

// SmartTelevision만의 메소드를 사용하려면 강제 변환 필요
if (rc instanceof SmartTelevision) {
    SmartTelevision smartTV = (SmartTelevision) rc;
    smartTV.internetConnect(); // SmartTelevision만의 메소드
}

🎭 다형성 구현

다형성이란?

사용 방법은 동일하지만 다양한 결과가 나오는 성질

interface Animal {
    void makeSound();
}

class Dog implements Animal {
    @Override
    public void makeSound() {
        System.out.println("멍멍!");
    }
}

class Cat implements Animal {
    @Override
    public void makeSound() {
        System.out.println("야옹~");
    }
}

// 다형성 활용
Animal[] animals = {new Dog(), new Cat()};
for (Animal animal : animals) {
    animal.makeSound(); // 같은 메소드 호출, 다른 결과
}
// 출력: 멍멍! 야옹~

현업에서의 활용

현업에서는 상속보다는 인터페이스를 통한 다형성을 더 많이 사용합니다. 왜냐하면:

  • 더 유연한 설계 가능
  • 다중 구현 지원
  • 결합도를 낮춤

🔧 매개변수의 다형성

인터페이스를 매개변수 타입으로 사용

public class AnimalShelter {
    // 매개변수를 인터페이스 타입으로 선언
    public void careAnimal(Animal animal) {
        animal.makeSound();
        // animal.eat(); // Animal 인터페이스에 있는 메소드만 사용 가능
    }
}

// 사용 예
AnimalShelter shelter = new AnimalShelter();
shelter.careAnimal(new Dog());    // Dog 객체 전달
shelter.careAnimal(new Cat());    // Cat 객체 전달
shelter.careAnimal(new Bird());   // Bird 객체 전달

💉 의존성 주입 (Dependency Injection)

생성자를 통한 의존성 주입 (권장)

public class AnimalService {
    private final Animal animal;
    
    // 생성자를 통한 의존성 주입
    public AnimalService(Animal animal) {
        this.animal = animal;
    }
    
    public void performService() {
        animal.makeSound();
    }
}

// 사용
AnimalService dogService = new AnimalService(new Dog());
AnimalService catService = new AnimalService(new Cat());

Setter를 통한 의존성 주입 (덜 권장)

public class AnimalService {
    private Animal animal;
    
    // Setter를 통한 의존성 주입
    public void setAnimal(Animal animal) {
        this.animal = animal;
    }
}

생성자 주입이 권장되는 이유:

  • 객체 생성 시점에 의존성이 확정됨
  • 불변성 보장 (final 키워드 사용 가능)
  • 의존성이 누락될 위험이 없음

💡 실전 활용 팁

1. 인터페이스 네이밍 컨벤션

// 형용사형 (-able, -ible)
interface Drawable, Comparable, Serializable

// 명사형
interface List, Set, Map

// 동작을 나타내는 경우
interface ActionListener, Observer

2. 인터페이스 분리 원칙 (Interface Segregation Principle)

// 나쁜 예 - 너무 많은 책임을 가진 인터페이스
interface Worker {
    void work();
    void eat();
    void sleep();
    void fly(); // 모든 Worker가 날 수 있는 건 아님
}

// 좋은 예 - 역할별로 분리
interface Worker {
    void work();
}

interface Eater {
    void eat();
}

interface Flyer {
    void fly();
}

// 필요한 인터페이스만 구현
class Robot implements Worker {
    @Override
    public void work() { /* 구현 */ }
}

class Bird implements Worker, Eater, Flyer {
    // 모든 메소드 구현
}

🎯 마무리

인터페이스는 Java에서 다형성을 구현하는 핵심 도구입니다.

핵심 포인트:

  1. 계약서 역할: "이 기능들을 구현하면 이런 혜택을 드립니다"
  2. 다형성 구현: 같은 방법으로 다양한 결과를 얻을 수 있음
  3. 유연한 설계: 구현체를 쉽게 교체할 수 있어 확장성이 뛰어남
  4. 결합도 감소: 구체적인 클래스가 아닌 인터페이스에 의존

현업에서는 인터페이스를 적극 활용하여 유지보수가 쉽고 확장 가능한 코드를 작성합니다. 특히 Spring Framework 같은 프레임워크에서는 인터페이스 기반의 설계가 필수적이에요!

반응형