[Java] 인터페이스

artp·2025년 3월 4일

java

목록 보기
21/32
post-thumbnail

인터페이스란?

인터페이스 개념 및 역할

인터페이스(interface)는 서로 다른 시스템이나 객체 간의 소통을 위한 접점을 제공하는 역할을 합니다.
쉽게 말해, 객체 간의 상호작용을 정의하는 규칙으로 사용됩니다.

사전적으로 두 장치를 연결하는 접속기라는 뜻을 가지며, 프로그래밍에서는 클래스가 따라야 할 일종의 "설계도" 역할을 합니다.

자바에서의 인터페이스 개념

자바에서는 클래스를 통한 다중 상속을 지원하지 않지만, 인터페이스를 활용하면 다중 상속의 장점을 구현할 수 있습니다.

인터페이스는 추상 메서드의 구현을 강제하여 코드의 일관성을 유지하도록 돕습니다. 뿐만 아니라, 다양한 프레임워크에서 클래스 간의 통신을 담당하며, 객체 지향 프로그래밍(OOP)에서 결합도(Coupling)을 낮추고 유지보수성을 높이는 디자인 패턴으로도 활용됩니다.

쉽게 말해, 인터페이스는 프로그램의 설계를 돕고, 더 유연한 코드 구조를 만드는 기법입니다.
우리가 반복되는 코드를 줄이기 위해 for문이나 while문을 사용하는 것처럼, 인터페이스는 설계의 이점을 극대화하기 위해 사용하는 개념이라고 볼 수 있습니다.

인터페이스는 클래스가 반드시 구현해야 하는 추상 메서드의 집합이며, 메서드의 구체적인 동작은 이를 구현하는 클래스에서 정의합니다.
즉, 인터페이스는 단순한 “규칙”을 정의하고, 실제 기능은 이를 구현하는 클래스에서 구현하는 방식입니다.

이러한 특징 덕분에 인터페이스는 추상화, 상속과 함께 다형성을 구현하는 객체 지향 프로그래밍(OOP)의 핵심 요소 중 하나로 자리 잡고 있습니다.

결국, 인터페이스를 활용하면 서로 다른 클래스들이 공통된 동작을 할 수 있도록 하면서도, 코드의 유연성과 확장성을 높일 수 있습니다.

인터페이스 구성 요소

1. 추상 메서드 (Abstract Method)

  • 인터페이스에는 메서드 선언만 있고, 본문(구현부)이 없는 추상 메서드를 포함합니다.
  • 인터페이스에 선언한 메서드는 abstract 키워드를 붙이지 않아도 자동으로 추상 메서드로 인식됩니다.
  • 인터페이스를 구현하는 클래스에서는 반드시 이 메서드를 오버라이딩하여 구현해야 합니다.

형식

interface Example {
	void abstractMethod(); // 추상 메서드 (public abstract 자동 추가)
}

2. 디폴트 메서드 (Default Method)

  • 인터페이스에 동작이 구현된 메서드로, default 키워드를 사용하여 선언합니다.
  • 구현 클래스에서 오버라이딩할 수 있으며, 기존 클래스의 구조를 변경하지 않고 기능을 추가할 때 유용합니다.

형식

interface Example {
	default void defaultMethod() {
    	System.out.println("디폴트 메서드 실행");
    }
}

3. 정적 메서드 (Static Method)

  • static 키워드를 사용하여 인터페이스 내부에서 정의된 정적 메서드입니다.
  • 인터페이스 이름으로 직접 호출할 수 있으며, 구현 클래스에서 오버라이딩할 수 없습니다.

형식

interface Example {
	static void staticMethod() {
    	System.out.println("정적 메서드 실행");
    }
}

사용 예제

Example.staticMethod(); // 인터페이스명으로 호출

4. 상수 (Constant)

  • 인터페이스에 선언된 필드는 자동으로 public static final이 적용됩니다.
  • 즉, 인터페이스의 필드는 변하지 않는 값(상수)만 가질 수 있습니다.
  • 해당 상수에 접근할 때는 인터페이스명을 사용합니다.

형식

interface Example {
	int VALUE = 100; // public static final 자동 적용
}

사용 예제

System.out.println(Example.VALUE); // 100

인터페이스 선언 형식

1. 인터페이스 선언 방법

인터페이스도 추상 클래스처럼 단독으로 인스턴스를 생성할 수 없습니다.
즉, 인터페이스는 반드시 구현(implements)하는 클래스가 있어야 하며, 해당 클래스에서 모든 추상 메서드를 오버라이딩하여 구현해야 합니다.

인터페이스 선언 형식

[접근제한자] interface 인터페이스명 { 
	자료형 변수명 =; // 상수 (public static final 자동 추가)
    반환형 메서드명(매개변수 ...); // 추상 메서드 (public abstract 자동 추가)
    default 반환형 메서드명(매개변수 ...) // 디폴트 메서드
    static 반환형 메서드명(매개변수 ...) // 정적 메서드
}
  • 인터페이스 내부의 모든 필드는 public static final이 자동으로 붙는 상수이며, 모든 메서드는 기본적으로 public abstract(추상 메서드)입니다.
  • Java 8 이후 default 메서드와 static 메서드가 추가되어 인터페이스 내에서 일부 구현을 제공할 수도 있습니다.

인터페이스 사용

1. 클래스에서 인터페이스 구현

인터페이스를 구현하려면, 클래스에서 implements 키워드를 사용해야 합니다.

  • 클래스는 하나의 부모만 extends(상속)할 수 있지만, 인터페이스는 여러 개를 implements하여 다중 구현이 가능합니다.
  • 인터페이스를 구현하면, 해당 인터페이스에 정의된 모든 추상 메서드를 반드시 오버라이딩해야 합니다.
  • 인터페이스는 객체를 생성할 수 없으며, 구현한 클래스의 객체를 통해서만 사용할 수 있습니다.
interface Animal {
	void makeSound(); // 추상 메서드
}

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

public class Main {
	public static void main(String[] args) {
    	Dog dog = new Dog();
        dog.makeSound(); // 멍멍!
    }
}
  • 클래스가 인터페이스를 구현하면, 인터페이스의 모든 추상 메서드를 반드시 구현해야 합니다.
  • 인터페이스는 "틀"만 제공하고, 실제 동작은 이를 구현하는 클래스에서 정의하게 됩니다.

2. 인터페이스 다중 구현

클래스는 다중 상속이 불가능하지만, 인터페이스는 여러 개를 동시에 구현(다중 구현)할 수 있습니다.
다중 구현을 통해 한 클래스가 여러 역할을 수행할 수 있도록 만들 수 있습니다.

interface Flyable {
	void fly();
}

interface Swimmable {
	void swim();
}

class Duck implements Flyable, Swimmable { // 인터페이스 다중 구현
	@Override
    public void fly() {
    	System.out.println("오리가 난다");
    }
    
    @Override
    public void swim() {
    	System.out.println("오리가 헤엄친다");
    }
}

public class Main {
	public static void main(String[] args) {
    	Duck duck = new Duck();
        duck.fly(); // 오리가 난다
        duck.swim(); // 오리가 헤엄친다
    }
}

3. 인터페이스끼리의 상속

클래스에서 extends 키워드를 사용해 상속을 하는 것처럼, 인터페이스끼리도 extends를 통해 확장할 수 있습니다.
즉, 기존 인터페이스를 확장하여 새로운 기능을 추가하는 방식으로 사용할 수 있습니다.

3.1 인터페이스 상속의 특징

  • 인터페이스는 여러 개의 부모 인터페이스를 동시에 상속(다중 상속)이 가능합니다.
  • 인터페이스는 메서드의 구현부가 없기 때문에, 인터페이스끼리 상속할 때 충돌 가능성이 없습니다.
  • 자식 인터페이스는 부모 인터페이스의 모든 메서드를 상속받으며, 이를 구현하는 클래스는 모든 메서드를 오버라이딩해야 합니다.
  • 인터페이스는 클래스와 달리 Object 클래스가 인터페이스의 최고 조상이 아니므로, 클래스를 상속할 수 없습니다.

3.2 인터페이스 상속 예제

interface A {
    void methodA();
}

interface B extends A { // 인터페이스 상속 (확장)
    void methodB();
}

class C implements B { // B를 구현하므로, A의 메서드도 구현해야 함
    @Override
    public void methodA() {
        System.out.println("A 메서드 구현");
    }

    @Override
    public void methodB() {
        System.out.println("B 메서드 구현");
    }
}

public class Main {
    public static void main(String[] args) {
        C obj = new C();
        obj.methodA(); // 출력: A 메서드 구현
        obj.methodB(); // 출력: B 메서드 구현
    }
}
  • B는 A를 상속받았기 때문에, B를 구현한 클래스 C는 A의 메서드도 반드시 구현해야 합니다.

4. 인터페이스 다중 상속

4.1. 인터페이스 다중 상속

4.2 인터페이스 다중 상속 예제

interface A {
	void methodA();
}

interface B extends A {
	void methodB();
}

// 인터페이스 C가 A와 B를 다중 상속
interface C extends A, B {
	void methodC();
}

// 클래스 D는 C를 구현하므로, A와 B의 메서드도 구현해야 함
class D implements C { 
	@Override
    public void methodA() {
    	System.out.println("A 메서드 구현");
    }
    
    @Override
    public void methodB() {
        System.out.println("B 메서드 구현");
    }

    @Override
    public void methodC() {
        System.out.println("C 메서드 구현");
    }
}

public class Main {
	public static void main(String[] args) {
    	D obj = new D();
        obj.methodA(); // A 메서드 구현
        obj.methodB(); // B 메서드 구현
        obj.methodC(); // C 메서드 구현
    }
}
  • 인터페이스끼리 상속하면, 하위 인터페이스는 부모 인터페이스의 모든 메서드를 포함하게 됩니다.
  • 따라서, 인터페이스를 구현하는 클래스는 부모와 자식 인터페이스의 모든 메서드를 구현해야 합니다.

4. 인터페이스끼리의 다중 상속이 가능한 이유

자바에서는 클래스의 다중 상속을 금지하지만, 인터페이스끼리의 다중 상속은 허용됩니다.
이는 인터페이스의 구조적 특성과 다중 상속에서 발생할 수 있는 문제를 해결할 수 있는 명확한 규칙이 존재하기 때문입니다.

4.1 자바에서 다중 상속이 제한된 이유

  • 클래스를 통한 다중 상속을 허용하면 메서드 출처가 모호해지는 문제가 발생할 수 있습니다.
  • 예를 들어, 부모 클래스 A와 B에 같은 메서드가 있다면, 자식 클래스에서 어느 부모의 메서드를 호출할지 모호합니다.
  • 이러한 문제를 방지하기 위해 자바에서는 클래스의 다중 상속을 금지하고, 인터페이스를 통한 다중 구현을 허용했습니다.

1. 인터페이스는 구현(메서드의 본문)이 없었음 (자바 8 이전)

자바 8 이전에는 인터페이스 내부에 abstract 메서드(추상메서드)만 선언할 수 있었습니다.
즉, 인터페이스끼리 상속해도 메서드의 구현(본문)이 없었기 때문에, 충돌 문제가 발생할 가능성이 없었습니다.
이로 인해 인터페이스 간 다중 상속을 허용해도 문제가 되지 않았습니다.

interface A {
	void methodA(); // 추상 메서드
}

interface B {
	void methodB(); // 추상 메서드
}

interface C extends A, B {
	void methodC();
}

2. 자바 8 이후 default 메서드 도입으로 인해 충돌 가능성이 생김

자바 8부터는 인터페이스에서 default 메서드를 통해 메서드의 기본 구현을 제공할 수 있게 되었습니다.
이는 기존의 인터페이스 개념을 확장하는 유용한 기능이지만, 동시에 다중 상속 시 메서드 충돌이 발생할 가능성을 만들었습니다.

interface A {
	default void showMessage() {
    	System.out.println("A 인터페이스의 메시지");
    }
}

interface B {
	default void showMessage() {
    	System.out.println("B 인터페이스의 메시지");
    }
}

// 인터페이스 C가 A와 B를 다중 상속
interface C extends A, B {
	// 충돌을 해결하기 위해 반드시 오버라이딩 필요
    @Override
    default void showMessage() {
    	System.out.println("C 인터페이스에서 충돌 해결");
    }
}

3. 충돌 발생 시 명확한 해결 방법 존재

인터페이스끼리 default 메서드가 충돌할 경우, 구현 클래스에서 반드시 해결해야 합니다.
이는 다중 상속에서 발생할 수 있는 모호성 문제를 방지하기 위한 설계 방식입니다.

즉, 인터페이스의 default 메서드가 충돌하면, 반드시 개발자가 직접 해결해야 하도록 강제되었습니다.

3.1 메서드를 오버라이딩하여 충돌 해결

interface A {
    default void showMessage() {
        System.out.println("A 인터페이스의 메시지");
    }
}

interface B {
    default void showMessage() {
        System.out.println("B 인터페이스의 메시지");
    }
}

// 인터페이스 C가 A와 B를 다중 상속 (extends 사용)
interface C extends A, B {
    // 충돌을 해결하기 위해 반드시 오버라이딩 필요
    @Override
    default void showMessage() {
        System.out.println("C 인터페이스에서 충돌 해결");
    }
}
  • 인터페이스 A와 B 모두 default 메서드 showMessage()를 가지고 있습니다.
  • 인터페이스 C가 A와 B를 동시에 extends하면 default 메서드 충돌이 발생합니다.
  • 이 문제를 해결하려면, C에서 showMessage()를 반드시 오버라이딩해야 합니다.

3.2 super 키워드를 사용하여 충돌 해결

interface A {
    default void showMessage() {
        System.out.println("A 인터페이스의 메시지");
    }
}

interface B {
    default void showMessage() {
        System.out.println("B 인터페이스의 메시지");
    }
}

// A와 B를 동시에 상속하는 인터페이스 C
interface C extends A, B {
    @Override
    default void showMessage() {
        B.super.showMessage();  // B 인터페이스의 showMessage()를 호출하여 충돌 해결
    }
}

// C를 구현하는 클래스 D
class D implements C {}

public class Main {
    public static void main(String[] args) {
        D obj = new D();
        obj.showMessage(); // B 인터페이스의 메시지
    }
}
  • super 키워드를 사용하여 특정 부모 인터페이스의 메서드를 호출합니다.

인터페이스의 특징

자바에서 인터페이스는 다른 클래스가 따라야 할 규칙을 정하는 기본 틀입니다. 즉, 다른 클래스 사이의 중간 매개 역할을 담당합니다.
클래스는 하나의 부모만 상속(다중 상속 금지)할 수 있지만, 여러 개의 인터페이스를 구현(다중 구현)할 수 있습니다.

클래스는 다중 상속이 불가능하지만, 인터페이스끼리는 다중 상속이 가능합니다.

또한, 인터페이스는 클래스와 다르게 객체를 직접 생성할 수 없으며, 반드시 구현 클래스를 통해 객체를 생성해야 합니다.

1. 인터페이스 구현 (클래스에서 implements 사용)

  • 인터페이스는 implements 키워드를 사용하여 구현합니다.
  • 클래스에서 인터페이스를 구현하면, 반드시 인터페이스의 추상 메서드를 오버라이딩해야 합니다.
  • 여러 개의 인터페이스를 동시에 구현(다중 구현)할 수 있습니다.

형식

class 클래스명 implements 인터페이스명 {}
class 자식클래스명 extends 부모클래스 implements 인터페이스1, 인터페이스2 {}

예제

interface Animal {
	void makeSound();
}

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

2. 인터페이스 다중 구현 (Multiple Implementation)

  • 자바는 클래스 다중 상속을 지원하지 않지만, 인터페이스는 여러 개를 동시에 구현할 수 있습니다.

형식

interface Flyable {
	void fly();
}
interface Swimmable {
	void swim();
}

class Dunk implements Flyable, Swimmable {
	@Override
    public void fly() {
    	System.out.println("오리가 납니다.");
    }
    @Override
    public void swim() {
    	System.out.println("오리가 헤엄칩니다.");
    }
}
  • 위 예시처럼, Flyable과 Swimmable을 동시에 구현할 수 있습니다.

3. 인터페이스 상속 (인터페이스끼리 extends 사용)

  • 인터페이스끼리는 extends를 사용하여 상속할 수 있습니다 (다중 상속 가능).
  • 이때는 구현하는 것이 아니라 상속의 개념이므로 implements 대신 extends를 사용합니다.

형식

interface 자식인터페이스명 extends 부모인터페이스 {}

예제

interface A {
	void methodA();
}
interface B extends A {
	void methodB();
}

class C implements B {
	@Override
    public void methodA() {
    	System.out.println("A 메서드 구현");
    }
    @Override
    public void methodB() {
    	System.out.println("B 메서드 구현");
    }
}
  • B는 A를 상속받아 methodA()로 포함하게 됩니다.
  • 결과적으로 C 클래스는 methodA()와 methodB()를 모두 구현해야 합니다.

4. 인터페이스는 static final 상수만 포함 가능

인터페이스에서 필드는 반드시 static final 상수로 선언됩니다.
즉, 인터페이스 내에서 일반적인 인스턴스 변수를 선언할 수 없으며, 오직 상수(static final)만 포함할 수 있습니다.

interface Constants {
	int MAX_SPEED = 120; // public static final 자동 적용
    String ERROR_MESSAGE = "에러 발생";
}

컴파일러가 자동으로 다음과 같이 변환합니다:

interface Constants {
    public static final int MAX_SPEED = 120;
    public static final String ERROR_MESSAGE = "에러 발생";
}

인터페이스끼리 다중 상속을 할 경우, 상수(static final 필드)는 그대로 상속됩니다.
즉, 하위 인터페이스에서도 부모 인터페이스의 상수를 그대로 사용할 수 있습니다.

interface A {
    int VALUE_A = 10;  // 상수 (public static final)
}

interface B extends A {
    int VALUE_B = 20;  // 추가 상수
}

public class Main {
    public static void main(String[] args) {
        System.out.println(B.VALUE_A); // 10 (A의 상수)
        System.out.println(B.VALUE_B); // 20 (B의 상수)
    }
}

5. 다중 상속에서 필드 충돌은 발생하지 않음

인터페이스의 필드는 static 이므로, 특정 인터페이스명을 명시하여 접근하면 됩니다.

즉, 다중 상속에서 동일한 이름의 필드가 있더라도 특정 인터페이스를 지정하여 사용하면 문제가 발생하지 않습니다.

interface A {
    int VALUE = 10;  // public static final 자동 적용
}

interface B {
    int VALUE = 20;  // public static final 자동 적용
}

// C는 A와 B를 동시에 상속
interface C extends A, B {}

public class Main {
    public static void main(String[] args) {
        System.out.println(A.VALUE); // 10
        System.out.println(B.VALUE); // 20
        // System.out.println(C.VALUE); // 컴파일 오류 (모호함)
    }
}
  • 특정 인터페이스(A.VALUE 또는 B.VALUE)를 명확히 지정해서 접근해야 합니다.
profile
donggyun_ee

0개의 댓글