코딩쿠의 코드 연대기

Java) 인터페이스 (interface) 본문

코딩스터디/JAVA스터디

Java) 인터페이스 (interface)

코딩쿠 2024. 11. 14. 23:01

학습목표

  • 인터페이스 정의하는 방법
  • 인터페이스 구현하는 방법
  • 인터페이스 레퍼런스를 통해 구현체를 사용하는 방법
  • 인터페이스 상속
  • 인터페이스의 기본 메소드 (Default Method), 자바 8
  • 인터페이스의 static 메소드, 자바 8
  • 인터페이스의 private 메소드, 자바 9

인터페이스(interface)란?

자바에서 인터페이스는 클래스가 어떤 동작을 가져야 하는지 정의하는 추상적인 자료형입니다. 쉽게 말해, 클래스가 따라야 할 규칙 또는 템플릿 같은 역할을 합니다. 인터페이스는 interface 키워드를 사용하여 선언하며, 메소드 시그니처 (메소드 이름, 매개변수, 반환 타입) 와 상수만 포함할 수 있습니다.

인터페이스의 특징

  • 추상 메소드: 인터페이스는 구현 코드가 없는 추상 메소드를 가집니다. 인터페이스를 구현하는 클래스는 이 추상 메소드들을 반드시 구현해야 합니다.
  • 상수: 인터페이스는 public static final 형태의 상수를 가질 수 있습니다.
  • 다중 상속: 자바 클래스는 단일 상속만 가능하지만, 인터페이스는 여러 개를 구현할 수 있습니다.
  • 다형성: 인터페이스를 통해 다형성을 구현할 수 있습니다. 즉, 같은 인터페이스를 구현하는 여러 클래스들을 인터페이스 타입으로 참조하여 동일한 메소드를 호출하더라도 각 클래스의 구현에 따라 다른 동작을 수행할 수 있습니다.

인터페이스 사용 예시

// Animal 인터페이스 정의
interface Animal {
  public void sound(); // 추상 메소드
}

// Dog 클래스가 Animal 인터페이스 구현
class Dog implements Animal {
  @Override
  public void sound() {
    System.out.println("Woof!");
  }
}

// Cat 클래스가 Animal 인터페이스 구현
class Cat implements Animal {
  @Override
  public void sound() {
    System.out.println("Meow!");
  }
}

위 예시에서 Animal 인터페이스는 sound() 라는 추상 메소드를 정의합니다. Dog 클래스와 Cat 클래스는 Animal 인터페이스를 구현하며, 각자의 방식으로 sound() 메소드를 구현합니다.

인터페이스의 장점

  • 개발 코드 수정 감소: 인터페이스를 사용하면 클래스 간의 의존성을 줄여 코드 수정을 최소화할 수 있습니다.
  • 유지보수 용이성 향상: 인터페이스를 통해 프로그램의 구조를 명확하게 하고, 변경 사항 발생 시 유지보수를 용이하게 합니다.
  • 재사용성 증가: 인터페이스를 구현하는 여러 클래스에서 동일한 기능을 재사용할 수 있습니다.
  • 다형성 지원: 인터페이스를 통해 다형성을 구현하여 프로그램의 유연성을 높일 수 있습니다.

Java 인터페이스 정의하는 방법

자바에서 인터페이스를 정의하는 방법은 클래스를 정의하는 방법과 유사하지만, 몇 가지 중요한 차이점이 있습니다.

1. interface 키워드 사용:

클래스를 정의할 때 class 키워드를 사용하는 것처럼, 인터페이스를 정의할 때는 interface 키워드를 사용합니다.

2. 추상 메소드 선언:

인터페이스는 주로 추상 메소드로 구성됩니다. 추상 메소드는 메소드의 시그니처(이름, 매개변수, 반환 타입)만 선언하고 구현은 포함하지 않습니다. 인터페이스의 모든 메소드는 기본적으로 public abstract이므로, 이 키워드들을 생략할 수 있습니다.

3. 상수 선언:

인터페이스는 상수를 포함할 수 있습니다. 상수는 기본적으로 public static final이므로, 이 키워드들을 생략할 수 있습니다.

4. 접근 제어자:

인터페이스는 public 또는 package-private 접근 제어자를 가질 수 있습니다. public 인터페이스는 어떤 클래스에서든 접근 가능하며, package-private 인터페이스는 동일한 패키지 내의 클래스에서만 접근 가능합니다.

예시:

Java

public interface Shape {
  double getArea(); // public abstract double getArea(); 와 동일
  double getPerimeter(); // public abstract double getPerimeter(); 와 동일

  double PI = 3.14159; // public static final double PI = 3.14159; 와 동일
}

위 예시에서 Shape 인터페이스는 getArea()getPerimeter()라는 두 개의 추상 메소드와 PI라는 상수를 정의합니다.

자바 8 이후:

자바 8부터는 인터페이스에 디폴트 메소드와 정적 메소드를 추가할 수 있게 되었습니다.

  • 디폴트 메소드: default 키워드를 사용하여 인터페이스에 기본 구현을 제공하는 메소드입니다.
  • 정적 메소드: static 키워드를 사용하여 인터페이스에서 직접 호출할 수 있는 메소드입니다.

Java 인터페이스 구현하는 방법

자바에서 인터페이스를 구현하는 방법은 다음과 같습니다.

1. implements 키워드 사용:

클래스에서 인터페이스를 구현하려면 implements 키워드를 사용합니다. 클래스는 여러 개의 인터페이스를 구현할 수 있으며, 각 인터페이스는 쉼표로 구분합니다.

2. 추상 메소드 구현:

인터페이스에 선언된 모든 추상 메소드를 구현해야 합니다. 메소드 시그니처 (이름, 매개변수, 반환 타입)는 인터페이스에 정의된 것과 정확히 일치해야 합니다.

3. @Override 어노테이션 (선택 사항):

구현된 메소드에 @Override 어노테이션을 추가하는 것이 좋습니다. 이 어노테이션은 컴파일러에게 해당 메소드가 인터페이스의 메소드를 재정의하는 것임을 알려주어, 실수로 메소드 시그니처를 잘못 작성했을 경우 컴파일 오류를 발생시킵니다.

예시:

Java

// Shape 인터페이스 (이전 예시에서 가져옴)
public interface Shape {
  double getArea();
  double getPerimeter();
}

// Circle 클래스가 Shape 인터페이스 구현
public class Circle implements Shape {
  private double radius;

  public Circle(double radius) {
    this.radius = radius;
  }

  @Override
  public double getArea() {
    return Math.PI * radius * radius;
  }

  @Override
  public double getPerimeter() {
    return 2 * Math.PI * radius;
  }
}

위 예시에서 Circle 클래스는 Shape 인터페이스를 구현하고, getArea()getPerimeter() 메소드를 구현합니다.

추가 사항:

  • 인터페이스의 모든 추상 메소드를 구현하지 않으면, 해당 클래스는 추상 클래스가 되어야 합니다.
  • 자바 8부터 인터페이스는 디폴트 메소드를 가질 수 있습니다. 디폴트 메소드는 인터페이스에 기본 구현을 제공하며, 구현 클래스에서 재정의할 수도 있습니다.
  • 인터페이스는 상속될 수 있습니다. 인터페이스를 상속하는 인터페이스는 부모 인터페이스의 모든 메소드를 상속받습니다.

Java 인터페이스 레퍼런스를 통해 구현체를 사용하는 방법

자바에서 인터페이스 레퍼런스를 통해 구현체를 사용하는 것은 다형성을 활용하는 핵심적인 방법입니다.

다형성이란?

같은 타입의 참조 변수를 사용하여 다양한 객체를 다루는 것을 의미합니다. 인터페이스를 사용하면, 인터페이스 타입의 참조 변수로 해당 인터페이스를 구현한 모든 객체를 참조할 수 있습니다.

인터페이스 레퍼런스를 통해 구현체를 사용하는 방법

  1. 인터페이스 타입의 변수 선언: 인터페이스 타입의 변수를 선언합니다.
  2. 구현체 객체 생성: 인터페이스를 구현한 클래스의 객체를 생성합니다.
  3. 변수에 객체 할당: 생성한 객체를 인터페이스 타입의 변수에 할당합니다.
  4. 메소드 호출: 인터페이스 타입의 변수를 통해 객체의 메소드를 호출합니다. 이때, 실제로 호출되는 메소드는 객체의 클래스에 정의된 메소드입니다.

예시:

Java

// Shape 인터페이스 (이전 예시에서 가져옴)
public interface Shape {
  double getArea();
  double getPerimeter();
}

// Circle 클래스와 Rectangle 클래스가 Shape 인터페이스 구현 (Rectangle 클래스는 생략)

public class Main {
  public static void main(String[] args) {
    Shape shape1 = new Circle(5); // Shape 타입의 변수에 Circle 객체 할당
    Shape shape2 = new Rectangle(4, 6); // Shape 타입의 변수에 Rectangle 객체 할당

    System.out.println("Circle Area: " + shape1.getArea()); // Circle 클래스의 getArea() 메소드 호출
    System.out.println("Rectangle Area: " + shape2.getArea()); // Rectangle 클래스의 getArea() 메소드 호출
  }
}

위 예시에서 shape1shape2 변수는 모두 Shape 타입이지만, 각각 Circle 객체와 Rectangle 객체를 참조합니다. getArea() 메소드를 호출하면, 실제로 호출되는 메소드는 각 객체의 클래스에 정의된 getArea() 메소드입니다.

장점:

  • 유연성: 코드를 수정하지 않고도 다양한 구현체를 사용할 수 있습니다.
  • 재사용성: 인터페이스를 통해 다양한 클래스에서 공통된 동작을 정의하고 재사용할 수 있습니다.
  • 테스트 용이성: 인터페이스를 사용하여 모의 객체를 만들어 테스트를 수행하기 용이합니다.

요약:

인터페이스 레퍼런스를 통해 구현체를 사용하면 다형성을 활용하여 유연하고 재사용 가능한 코드를 작성할 수 있습니다. 이는 객체 지향 프로그래밍의 핵심 개념 중 하나이며, 자바에서 인터페이스를 사용하는 주된 이유 중 하나입니다.

Java 인터페이스 상속

자바에서 인터페이스는 클래스처럼 상속이 가능합니다. 인터페이스 상속은 기존 인터페이스를 확장하여 새로운 인터페이스를 정의하는 것을 의미합니다.

인터페이스 상속의 특징

  • extends 키워드 사용: 인터페이스 상속에는 extends 키워드를 사용합니다.
  • 다중 상속 가능: 인터페이스는 클래스와 달리 여러 개의 인터페이스를 상속할 수 있습니다.
  • 추상 메소드 상속: 부모 인터페이스의 모든 추상 메소드는 자식 인터페이스에 상속됩니다.
  • 디폴트 메소드 상속: 자바 8부터 인터페이스는 디폴트 메소드를 가질 수 있습니다. 자식 인터페이스는 부모 인터페이스의 디폴트 메소드를 상속받으며, 필요에 따라 재정의할 수 있습니다.
  • 상수 상속: 부모 인터페이스의 상수도 자식 인터페이스에 상속됩니다.

예시:

Java

// Animal 인터페이스
interface Animal {
  void sound();
}

// Pet 인터페이스가 Animal 인터페이스 상속
interface Pet extends Animal {
  void play();
}

// Dog 클래스가 Pet 인터페이스 구현
class Dog implements Pet {
  @Override
  public void sound() {
    System.out.println("Woof!");
  }

  @Override
  public void play() {
    System.out.println("Fetch the ball!");
  }
}

위 예시에서 Pet 인터페이스는 Animal 인터페이스를 상속받아 sound() 메소드를 상속받고, play() 메소드를 추가로 정의합니다. Dog 클래스는 Pet 인터페이스를 구현하여 sound() 메소드와 play() 메소드를 모두 구현해야 합니다.

인터페이스 상속의 장점

  • 코드 재사용: 기존 인터페이스를 확장하여 새로운 인터페이스를 정의함으로써 코드의 중복을 줄일 수 있습니다.
  • 확장성: 인터페이스 상속을 통해 인터페이스를 계층적으로 구성하고, 기능을 점진적으로 추가할 수 있습니다.
  • 유연성: 인터페이스 상속을 통해 다양한 인터페이스 조합을 만들어 사용할 수 있습니다.

요약:

자바에서 인터페이스는 extends 키워드를 사용하여 상속할 수 있습니다. 인터페이스 상속은 코드 재사용, 확장성, 유연성을 높이는 데 도움이 됩니다. 다중 상속이 가능하며, 부모 인터페이스의 모든 멤버를 상속받습니다.

Java 인터페이스의 기본 메소드 (Default Method), 자바 8

자바 8부터 인터페이스에 기본 메소드(Default Method)라는 새로운 기능이 추가되었습니다. 기본 메소드는 인터페이스에 기본 구현을 제공하는 메소드입니다. 즉, 인터페이스 내에서 메소드의 바디를 정의할 수 있게 된 것입니다.

기본 메소드를 사용하는 이유

  • 인터페이스의 확장: 기존 인터페이스에 새로운 메소드를 추가해야 할 때, 기존 구현체들을 수정하지 않고도 새로운 메소드를 추가할 수 있습니다.
  • 하위 호환성 유지: 자바 라이브러리의 인터페이스에 새로운 메소드를 추가할 때, 기존 코드와의 호환성을 유지하면서 인터페이스를 확장할 수 있습니다.

기본 메소드 선언

기본 메소드는 default 키워드를 사용하여 선언합니다.

Java

interface MyInterface {
  void abstractMethod(); // 추상 메소드

  default void defaultMethod() {
    System.out.println("This is a default method.");
  }
}

기본 메소드 사용

인터페이스를 구현하는 클래스는 기본 메소드를 상속받아 사용할 수 있습니다. 또한, 필요에 따라 기본 메소드를 재정의할 수도 있습니다.

Java

class MyClass implements MyInterface {
  @Override
  public void abstractMethod() {
    System.out.println("This is an abstract method.");
  }

  // defaultMethod()는 상속받아 사용하거나 재정의할 수 있습니다.
}

다중 상속과 기본 메소드

클래스가 여러 인터페이스를 구현하고, 각 인터페이스에 같은 시그니처의 기본 메소드가 있는 경우 충돌이 발생할 수 있습니다. 이 경우, 클래스는 명시적으로 어떤 인터페이스의 기본 메소드를 사용할지 지정해야 합니다.

Java

interface InterfaceA {
  default void sameMethod() {
    System.out.println("InterfaceA's sameMethod()");
  }
}

interface InterfaceB {
  default void sameMethod() {
    System.out.println("InterfaceB's sameMethod()");
  }
}

class MyClass implements InterfaceA, InterfaceB {
  @Override
  public void sameMethod() {
    InterfaceA.super.sameMethod(); // InterfaceA의 sameMethod() 사용
  }
}

장점

  • 인터페이스를 확장하면서 기존 코드와의 호환성을 유지할 수 있습니다.
  • 인터페이스에 기본 구현을 제공하여 코드의 중복을 줄일 수 있습니다.

단점

  • 다중 상속 시 기본 메소드 충돌 문제가 발생할 수 있습니다.
  • 인터페이스의 추상적인 특성을 약화시킬 수 있다는 우려가 있습니다.

요약

자바 8의 기본 메소드는 인터페이스에 기본 구현을 제공하는 유용한 기능입니다. 인터페이스를 확장하고, 하위 호환성을 유지하며, 코드의 중복을 줄이는 데 도움이 됩니다. 하지만 다중 상속 시 충돌 문제에 유의해야 합니다.

Java 인터페이스의 static 메소드, 자바 8

자바 8부터 인터페이스에 정적 메소드(Static Method)를 정의할 수 있게 되었습니다. 정적 메소드는 인터페이스 자체에 속하는 메소드로, 인터페이스 이름을 통해 직접 호출할 수 있습니다.

정적 메소드를 사용하는 이유

  • 헬퍼 메소드 제공: 인터페이스와 관련된 유틸리티 함수를 제공할 수 있습니다.
  • 코드 재사용: 인터페이스를 구현하는 여러 클래스에서 공통적으로 사용하는 코드를 정적 메소드로 정의하여 재사용할 수 있습니다.
  • 인터페이스 관련 기능을 그룹화: 인터페이스와 관련된 기능을 인터페이스 내에 그룹화하여 코드의 가독성을 높일 수 있습니다.

정적 메소드 선언

정적 메소드는 static 키워드를 사용하여 선언합니다.

Java

interface MyInterface {
  void abstractMethod(); // 추상 메소드

  static void staticMethod() {
    System.out.println("This is a static method.");
  }
}

정적 메소드 호출

정적 메소드는 인터페이스 이름을 통해 직접 호출합니다.

Java

MyInterface.staticMethod();

특징

  • 객체 생성 없이 호출 가능: 정적 메소드는 인터페이스의 인스턴스를 생성하지 않고도 호출할 수 있습니다.
  • 구현 클래스에서 접근 가능: 인터페이스를 구현하는 클래스에서 정적 메소드에 접근할 수 있습니다.
  • 재정의 불가능: 구현 클래스에서 정적 메소드를 재정의할 수 없습니다.
  • 다른 정적 메소드 호출 가능: 인터페이스의 정적 메소드는 다른 정적 메소드를 호출할 수 있습니다.

예시

Java

interface MathUtils {
  static int sum(int a, int b) {
    return a + b;
  }

  static int multiply(int a, int b) {
    return a * b;
  }
}

public class Main {
  public static void main(String[] args) {
    int sum = MathUtils.sum(5, 3);
    int product = MathUtils.multiply(5, 3);

    System.out.println("Sum: " + sum); // 출력: Sum: 8
    System.out.println("Product: " + product); // 출력: Product: 15
  }
}

위 예시에서 MathUtils 인터페이스는 sum()multiply()라는 두 개의 정적 메소드를 정의합니다. Main 클래스에서는 MathUtils 인터페이스 이름을 통해 정적 메소드를 직접 호출하여 사용합니다.

요약

자바 8의 정적 메소드는 인터페이스에 유틸리티 함수를 제공하고 코드를 재사용하는 데 유용한 기능입니다. 인터페이스 관련 기능을 그룹화하여 코드의 가독성을 높일 수 있습니다. 객체 생성 없이 인터페이스 이름을 통해 직접 호출할 수 있으며, 구현 클래스에서 재정의할 수 없습니다.

Java 인터페이스의 private 메소드, 자바 9

자바 9부터 인터페이스에 private 메소드를 추가할 수 있게 되었습니다. private 메소드는 인터페이스 내부에서만 사용할 수 있는 메소드로, 인터페이스의 구현 세부 사항을 캡슐화하고 코드 중복을 줄이는 데 도움을 줍니다.

private 메소드를 사용하는 이유

  • 코드 중복 제거: 여러 기본 메소드에서 공통적으로 사용하는 코드를 private 메소드로 추출하여 중복을 제거할 수 있습니다.
  • 캡슐화: 인터페이스의 내부 구현 로직을 외부에 노출하지 않고 숨길 수 있습니다.
  • 코드 가독성 향상: private 메소드를 사용하여 기본 메소드의 코드를 더 간결하고 명확하게 만들 수 있습니다.

private 메소드 선언

private 메소드는 private 키워드를 사용하여 선언하며, static 또는 인스턴스 메소드로 선언할 수 있습니다.

Java

interface MyInterface {
  default void defaultMethod1() {
    // ...
    privateMethod(); // private 인스턴스 메소드 호출
    // ...
  }

  default void defaultMethod2() {
    // ...
    privateStaticMethod(); // private 정적 메소드 호출
    // ...
  }

  private void privateMethod() {
    // ...
  }

  private static void privateStaticMethod() {
    // ...
  }
}

private 메소드 사용

private 메소드는 인터페이스 내부에서만 호출할 수 있습니다. 외부 클래스나 인터페이스를 구현하는 클래스에서는 private 메소드에 접근할 수 없습니다.

예시

Java

interface StringModifier {
  default String trimAndUpperCase(String str) {
    String trimmed = trim(str);
    return toUpperCase(trimmed);
  }

  default String trimAndLowerCase(String str) {
    String trimmed = trim(str);
    return toLowerCase(trimmed);
  }

  private String trim(String str) {
    return str.trim();
  }

  private String toUpperCase(String str) {
    return str.toUpperCase();
  }

  private String toLowerCase(String str) {
    return str.toLowerCase();
  }
}

위 예시에서 trimAndUpperCase()trimAndLowerCase() 메소드는 모두 문자열을 다듬고 대소문자를 변경하는 기능을 제공합니다. 이 두 메소드에서 공통적으로 사용하는 trim(), toUpperCase(), toLowerCase() 메소드를 private 메소드로 정의하여 코드 중복을 제거하고 캡슐화했습니다.

요약

자바 9의 private 메소드는 인터페이스의 구현 세부 사항을 캡슐화하고 코드 중복을 줄이는 데 유용한 기능입니다. private 메소드는 인터페이스 내부에서만 사용할 수 있으며, 외부 클래스에서는 접근할 수 없습니다.

결론

인터페이스는 자바 프로그래밍에서 중요한 개념입니다. 인터페이스를 사용하면 프로그램의 유연성, 재사용성, 유지보수성을 향상시킬 수 있습니다. 다형성을 구현하고, 클래스 간의 의존성을 줄이는 데에도 효과적입니다.

'코딩스터디 > JAVA스터디' 카테고리의 다른 글

Java 멀티쓰레드 프로그래밍  (2) 2024.11.16
Java 예외 처리  (4) 2024.11.15
Java 패키지  (6) 2024.11.13
Java) 상속_객체지향 프로그래밍(OOP)  (6) 2024.11.12
java 자료구조) 이진트리 (BinrayTree)  (0) 2024.11.11