JAVA

자바 인터페이스

euicheol0910 2025. 4. 22. 16:06

인터페이스란?

 

자식 클래스가 여러 부모 클래스를 상속받을 수 있다면, 다양한 동작을 수행할 수 있다는 장점을 가지게 될 것이다.
하지만 클래스를 이용하여 다중 상속을 할 경우 메소드 출처의 모호성 등 여러 가지 문제가 발생할 수 있어 자바에서는 클래스를 통한 다중 상속은 지원하지 않는다.

 

class Father {
    void speak() {
        System.out.println("Father speaking");
    }
}

class Mother {
    void speak() {
        System.out.println("Mother speaking");
    }
}

class Child extends Father, Mother {   
	// ❗ 자바에서는 이렇게 다중 상속 불가
    // 아무 것도 안 써도 Father, Mother 둘 다 상속받았다고 가정
}

 

 

 

 

하지만 다중 상속의 이점을 버릴 수는 없기에 자바에서는 인터페이스라는 것을 통해 다중 상속을 지원한다.


인터페이스(interface)란 다른 클래스를 작성할 때 기본이 되는 틀을 제공하면서, 다른 클래스 사이의 중간 매개 역할까지 담당하는 일종의 추상 클래스를 의미한다.

 

 

 

인터페이스의 선언은 다음과 같다. 

 

접근제어자 interface 인터페이스이름 {

    public static final 타입 상수이름 = 값;

    ...

    public abstract 메소드이름(매개변수목록);

    ...

}

 

일반 클래스와는 달리 인터페이스의 모든 필드는 public static final이어야 하며, 모든 메소드는 public abstract이어야 한다. 또한 인터페이스는 추상 클래스와 마찬가지로 자신이 직접 인스턴스를 생성할 수는 없으며 인터페이스가 포함하고 있는 추상 메소드를 구현해 줄 클래스를 작성해야만 한다.

 

 

interface Flyable {
    void fly(); // 무조건 구현해야 함
}

interface Swimable {
    void swim(); // 무조건 구현해야 함
}

class Duck implements Flyable, Swimable {
    @Override
    public void fly() {
        System.out.println("오리가 날아갑니다.");
    }

    @Override
    public void swim() {
        System.out.println("오리가 헤엄칩니다.");
    }
}

 

위와 같은 인터페이스를 통해 구현한 클래스에서 특정 기능을 (메소드) 구현하도록 강제한다. 그렇다면 인터페이스를 사용하면 어떤 점이 좋은걸까?

 

1. 인터페이스는 협업을 용이하게 해준다.

 

다음과 같이 Person이라는 인터페이스가 있다고 하면, 이를 implements한 클래스는 introduce라고 하는 자기소개 추상메서드를 반드시 구현해야한다.

interface Person {
    void introduce(); // 모든 사람은 자기소개를 해야 한다!
}

 

 

 

 

아래와 같이 Student,Teacher,Engineer는 메소드 구조를 "강제"당하게 되고, 여러 사람이 협업할 때, 각자 다른 클래스라도 "똑같은 규칙"을 따라야 한다는 것을 확인할 수 있다. 덕분에 나중에 코드를 재활용하거나, 유지보수할 때 통일성이 생겨서 훨씬 편리해진다. 밑에서 더 자세히 설명해보자면,

class Student implements Person {
    @Override
    public void introduce() {
        System.out.println("저는 학생입니다.");
    }
}

class Teacher implements Person {
    @Override
    public void introduce() {
        System.out.println("저는 선생님입니다.");
    }
}

class Engineer implements Person {
    @Override
    public void introduce() {
        System.out.println("저는 엔지니어입니다.");
    }
}

 

 

 

코드를 재활용하기 쉽다는 이유는 공통 인터페이스를 이해하고만 있다면, 다형성을 이용해 Student, Teacher, Engineer가 어떤 객체든 다 똑같은 방식으로 사용할 수 있다.

 

 

 

public class Main {
    public static void main(String[] args) {
        Person p1 = new Student();
        Person p2 = new Teacher();
        Person p3 = new Engineer();

        p1.introduce();  // 학생 소개
        p2.introduce();  // 선생님 소개
        p3.introduce();  // 엔지니어 소개
    }
}

 

introduce()를 호출하는 코드는 한 줄이면 되며 객체(Student/Teacher/Engineer)가 뭐든 코드 구조는 안 바꿔도 된다. 즉,  "코드를 새로 짜지 않고, 미리 짜놓은 패턴대로 여러 클래스를 만들어서 재활용하는 것"이다. 

 

 

2. 인터페이스는 변경, 교체에 용이하다 

 

 

interface Payment {
    void pay(int amount);
}

class CardPayment implements Payment {
    @Override
    public void pay(int amount) {
        System.out.println(amount + "원을 카드로 결제했습니다.");
    }
}

class CashPayment implements Payment {
    @Override
    public void pay(int amount) {
        System.out.println(amount + "원을 현금으로 결제했습니다.");
    }
}

class KakaoPayPayment implements Payment {
    @Override
    public void pay(int amount) {
        System.out.println(amount + "원을 카카오페이로 결제했습니다.");
    }
}

 

Payment 인터페이스로 "돈을 지불한다(pay)" 라는 "기능"만 약속하고 카드 결제,현금 결제,카카오페이 결제라는 다양한 방식으로 구현했다.

 

public class Main {
    public static void main(String[] args) {
        Payment payment = new CardPayment(); // 초기에는 카드 결제
        payment.pay(10000); // "10000원을 카드로 결제했습니다."
    }
}

 

Main 코드에서 Payment 인터페이스만 보고 프로그래밍한다. 실제 객체가 CardPayment인지는 신경 안 쓴다.

 

Payment.pay(10000); 이렇게 호출할 때, Main은 얘가 어떤 방식으로 결제하나 몰라도 된다.
그냥 "pay 메소드가 있을 거고, 알아서 결제할 거야" 라고 믿고 쓰는 것이다.
=> "Payment라는 규칙(인터페이스)만 믿고 코딩하는 것"

 

 

public class KakaoPay {
    public void pay(int amount) {
        System.out.println("카카오페이로 결제: " + amount + "원");
    }
}

public class PaymentService {
    private KakaoPay kakaoPay = new KakaoPay();

    public void doPayment(int amount) {
        kakaoPay.pay(amount);
    }
}

 

 

위와 같이 코드를 이렇게 짜면 무조건 카카오페이만 사용해야 한다.

만약 TossPay로 바꾸려면 PaymentService 내부도 수정해야한다.

 

 

 

public interface PayMethod {
    void pay(int amount);
}

public class KakaoPay implements PayMethod {
    public void pay(int amount) {
        System.out.println("카카오페이로 결제: " + amount + "원");
    }
}

public class TossPay implements PayMethod {
    public void pay(int amount) {
        System.out.println("토스로 결제: " + amount + "원");
    }
}

public class PaymentService {
    private PayMethod payMethod;

    public PaymentService(PayMethod payMethod) {
        this.payMethod = payMethod;
    }

    public void doPayment(int amount) {
        payMethod.pay(amount);
    }
}

 

하지만 다음과 같이 인터페이스를 활용할 경우, 이제는 KakaoPay, TossPay 아무거나 전달만 하면 되며,

PaymentService 코드 절대 안 고쳐도 된다.

 

즉, 인터페이스로 인해 다음과 같은 효과를 얻을 수 있다.

  • 구현 클래스를 갈아끼우기 쉬움
  • 유지보수 시 다른 클래스에 영향 없음
  • 결합도는 낮아지고, 유연성은 높아짐

 

 

'JAVA' 카테고리의 다른 글

JAVA 어노테이션  (0) 2025.04.28
자바의 제네릭에 대하여  (3) 2025.04.23
자바 오버로딩 , 오버라이딩  (2) 2025.04.18
자바의 특성(장점)  (2) 2025.04.17
JVM이란?  (4) 2025.04.16