CodeGym /행동 /JAVA 25 SELF /인터페이스의 개념과 선언 문법

인터페이스의 개념과 선언 문법

JAVA 25 SELF
레벨 20 , 레슨 0
사용 가능

1. 인터페이스란 무엇인가?

추상 클래스가 집의 설계도라면, 인터페이스는 특정 작업 수행에 대한 계약에 더 가깝습니다. 예를 들어, 배관공을 부르면 “수도꼭지 고치기”와 “누수 해결”을 기대하죠. 어떻게 하는지는 중요하지 않고 결과만 나오면 됩니다. Java에서 인터페이스는 바로 그런 계약입니다. 어떤 메서드들이 구현되어야 하는지를 말해 주지만 어떻게 구현할지는 말하지 않습니다.

간단히 핵심만:

인터페이스는 Java의 특별한 타입으로, 이 인터페이스에 서명한(구현하기로 한) 클래스가 반드시 구현해야 하는 메서드들의 집합을 정의합니다.

  • 인터페이스는 “무엇을 할지”만 설명하고, “어떻게 할지”는 설명하지 않습니다.
  • Java 8 이전에는 인터페이스가 메서드 구현을 포함하지 않았습니다.
  • 인터페이스는 순수한 계약입니다. 클래스가 인터페이스를 구현한다면 그 메서드들을 모두 구현해야 합니다.

왜 편리할까요?

  • 하나의 클래스에 여러 인터페이스를 “붙여” 여러 “역할”을 부여할 수 있습니다.
  • 클래스가 다양한 기능을 구현할 수 있게 하여 유연하고 확장 가능한 아키텍처를 구축할 수 있습니다.
  • 인터페이스는 Java 표준 라이브러리에서 광범위하게 사용됩니다(예: Comparable, Serializable, Runnable 등).

2. 인터페이스 선언 문법

Java에서 인터페이스를 선언하는 것은 아주 간단합니다. 키워드 interface를 사용합니다. Java 8 이전에는 인터페이스의 메서드가 기본적으로 public abstract로 간주되었고, 이를 명시적으로 쓰지 않아도 됩니다. 즉, 인터페이스를 구현하는 클래스에서 해당 메서드를 구현해야 한다는 뜻입니다.

인터페이스 예시


public interface Movable {
    void move(int x, int y);
}
  • 여기서는 Movable 인터페이스를 선언했습니다(“움직일 수 있는” 정도로 해석할 수 있습니다).
  • 내부에는 move(int x, int y) 메서드가 선언되어 있습니다. 메서드 본문은 없고 시그니처만 있습니다. 이것이 바로 “계약”입니다. “Movable을 구현한다면 move를 구현하라.”

문법상의 특징

  • 인터페이스의 메서드는 본문이 없습니다(Java 8 이전).
  • 인터페이스의 모든 메서드는 기본적으로 public abstract입니다(명시하지 않아도 됨).
  • 인터페이스에는 상수(public static final)만 포함할 수 있고, 일반 필드는 둘 수 없습니다.

상수 예시

public interface Constants {
    int MAX_SPEED = 100; // 기본적으로 public static final
}

3. 인터페이스와 클래스의 차이

인터페이스는 클래스가 아닙니다! 무엇이 다른지 살펴봅시다.

클래스(추상 클래스 포함) 인터페이스
필드(상태)를 가질 수 있음 필드를 가질 수 없음(상수만 가능)
메서드 구현을 포함할 수 있음 Java 8 이전에는 구현을 포함할 수 없고 시그니처만 가능
new로 생성 가능(추상 클래스가 아니면) 직접 생성할 수 없음
상속은 단일 상속만 가능(extends) 클래스는 여러 인터페이스를 구현할 수 있음(implements)
‘무엇인가’를 표현하는 데 사용 ‘무엇을 할 수 있는가’를 표현하는 데 사용

클래스는 implements 로 인터페이스를 구현합니다

public class Robot implements Movable {
    @Override
    public void move(int x, int y) {
        System.out.println("로봇이 좌표 (" + x + ", " + y + ")로 이동합니다");
    }
}
  • Robot 클래스는 Movable 인터페이스를 구현합니다.
  • implements 키워드는 “구현한다”는 의미입니다.
  • 인터페이스의 모든 메서드를 반드시 구현해야 합니다(그렇지 않으면 컴파일 오류).
  • @Override 애노테이션은 필수는 아니지만, 컴파일러와 개발자 모두에게 실수를 줄이는 데 도움이 됩니다.

4. 인터페이스 사용 예

간단한 예제를 작성하여 실제로 어떻게 동작하는지 살펴봅시다. Movable 인터페이스가 있고, 로봇, 자동차, 동물 등 서로 다른 클래스가 “이동”할 수 있도록 만들고 싶다고 가정해 봅시다.

1단계. 인터페이스 선언

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

2단계. 클래스에서 인터페이스 구현

public class Robot implements Movable {
    @Override
    public void move(int x, int y) {
        System.out.println("로봇이 좌표 (" + x + ", " + y + ")로 이동합니다");
    }
}

3단계. 인터페이스 사용

public class Main {
    public static void main(String[] args) {
        Movable m = new Robot(); // 인터페이스 타입 변수!
        m.move(10, 20); // 출력: 로봇이 좌표 (10, 20)로 이동합니다
    }
}

참고

  • 인터페이스 타입 변수(Movable m)를 선언하고, 그 인터페이스를 구현하는 클래스의 객체(new Robot())를 참조하게 할 수 있습니다.
  • 이렇게 하면 실제 클래스가 무엇인지 몰라도, “이동할 수 있는” 어떤 객체와도 동작하는 범용 코드를 작성할 수 있습니다.

메서드를 구현하지 않으면 컴파일 오류

Error: Class 'Robot' must either be declared abstract or implement abstract method 'move(int, int)' in 'Movable'

이것이 인터페이스의 힘입니다 — 모든 구현 클래스가 필요한 메서드를 갖추도록 보장합니다.

5. Java 표준 라이브러리의 인터페이스

인터페이스는 단지 “기초 이론”이 아닙니다. Java 표준 라이브러리 전반에서 적극적으로 사용됩니다. 몇 가지 예를 봅시다:

  • Comparable<T> — 객체를 비교하기 위한 인터페이스(예: 정렬 시).
  • Runnable — 스레드를 시작하기 위한 인터페이스.
  • Serializable — 객체를 직렬화할 수 있음을 나타내는 마커 인터페이스.
  • List, Set, Map — 컬렉션 인터페이스.

예시: Comparable

public class Person implements Comparable<Person> {
    String name;
    int age;

    // 생성자 및 기타 메서드...

    @Override
    public int compareTo(Person other) {
        return this.age - other.age;
    }
}

이제 Person 객체들은 Comparable 인터페이스를 구현하므로 정렬할 수 있습니다.

6. 시각적 다이어그램: 인터페이스가 동작하는 방식

+-------------------+         +-------------------+
|   interface       |         |    class          |
|   Movable         |<--------|    Robot          |
|-------------------|         |-------------------|
| +move(int, int)   |         | +move(int, int)   |
+-------------------+         +-------------------+
  • 화살표는 Robot 클래스가 Movable 인터페이스를 구현함을 나타냅니다.
  • 인터페이스는 “무엇이 있어야 하는가”만 정의하고, 클래스는 “어떻게 동작하는가”를 정의합니다.

생활 속 비유

Java의 인터페이스는 운전면허와 같습니다. 면허(“운전자” 인터페이스를 구현)를 가지고 있다면 자동차를 운전할 줄 안다는 뜻입니다. 정확히 어떻게 운전하는지는 사람마다 다를 수 있지만(누군가는 신중하게, 누군가는 빠르게), 중요한 것은 그 능력이 있음을 약속했다는 점입니다.

7. 실전 예제: 우리 애플리케이션 확장

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

public class Animal implements Movable {
    protected String name;

    public Animal(String name) {
        this.name = name;
    }

    @Override
    public void move(int x, int y) {
        System.out.println(name + "이(가) 좌표 (" + x + ", " + y + ")로 이동합니다");
    }
}

public class Robot implements Movable {
    private String model;

    public Robot(String model) {
        this.model = model;
    }

    @Override
    public void move(int x, int y) {
        System.out.println("로봇 " + model + "이(가) 좌표 (" + x + ", " + y + ")로 이동합니다");
    }
}

public class Main {
    public static void main(String[] args) {
        Movable[] movables = {
            new Animal("바르식"),
            new Robot("R2D2")
        };

        for (Movable m : movables) {
            m.move(5, 10);
        }
    }
}

결과:

바르식이(가) 좌표 (5, 10)로 이동합니다
로봇 R2D2이(가) 좌표 (5, 10)로 이동합니다

우리는 Movable 타입의 객체 배열을 만들었습니다. 이 배열에는 해당 인터페이스를 구현하는 어떤 객체라도 들어갈 수 있습니다! 루프 속 코드는 실제로 그것이 동물인지 로봇인지 몰라도, 각 객체가 move를 할 수 있다는 사실만 알면 됩니다.

8. 인터페이스 사용 시 흔한 실수

오류 № 1: 인터페이스 객체를 직접 생성하려는 시도. 인터페이스는 계약일 뿐 구체 객체가 아닙니다. new Movable()처럼 쓸 수 없으며, 그렇게 하면 컴파일 오류가 발생합니다. 인터페이스를 구현한 클래스의 객체를 생성해야 합니다.

오류 № 2: 인터페이스의 모든 메서드를 구현하지 않음. 클래스가 인터페이스를 구현하면서 모든 메서드를 구현하지 않으면 컴파일러가 “Class must either be declared abstract or implement abstract method ...” 오류를 냅니다. 모든 메서드를 구현하고 싶지 않다면 클래스에 abstract를 선언하세요.

오류 № 3: 접근 제한자를 잊음. 인터페이스의 메서드는 항상 public입니다(명시하지 않아도). 구현 클래스에서는 접근 수준을 낮출 수 없으므로 해당 메서드도 public이어야 합니다.

오류 № 4: 인터페이스에 일반 필드를 추가하려는 시도. 인터페이스에서는 상수(public static final)만 선언할 수 있습니다. 일반(비정적) 필드는 추가할 수 없습니다.

코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION