인터페이스

smj_716·2025년 2월 3일

Java-study / live-study

목록 보기
8/16

1. 인터페이스

1) 인터페이스란?

인터페이스는 일종의 추상클래스와 비슷한 개념으로, 추상 메서드만을 가질 수 있는 구조다. 하지만 추상 클래스보다 추상화 정도가 높다.

즉, 인터페이스는 일반 메서드나 멤버 변수를 가질 수 없으며, 오직 추상 메서드(자바 8 이후부터는 예외적으로 default, static, private 메서드 포함)를 가질 수 있다.

2) 인터페이스의 장점

  • 개발 시간 단축
    인터페이스가 작성되면 이를 사용해서 프로그램을 작성하는 것이 가능하다.메서드를 호출하는 쪽에서는 선언부만 알면 되기 때문이다.
  • 표준화 가능
    프로젝트에 사용되는 기본 틀을 인터페이스로 작성한 다음, 개발자들에게 인터페이스를 구현하여 프로그램을 작성하도록 하면 보다 일관되고 정형화된 프로그램의 개발이 가능하다.
  • 서로 관계없는 클래스들 간의 관계를 맺어준다
    하나의 인터페이스를 공통적으로 구현하도록 하여 관계를 맺어 줄 수 있다.
  • 독립적인 프로그래밍이 가능
    인터페이스를 이용하면 클래스의 선언과 구현을 분리시킬 수 있기 때문에 실제 구현에 독립적인 프로그램을 작성하는 것이 가능하다.

3) 인터페이스 특징

  • 모든 메서드는 기본적으로 public abstract이다.
  • 모든 필드는 기본적으로 public static final이다.
  • 다중 구현(다중 상속)이 가능하다.
  • default 메서드를 통해 기본적인 구현을 제공할 수 있다. (Java 8 이후)
  • static 메서드를 포함할 수 있다. (Java 8 이후)
  • private 메서드를 포함할 수 있다. (Java 9 이후)

2. 인터페이스 정의하는 방법

interface 인터페이스 {          
      public static final 타입 상수이름 =;         
      public abstract 메소드이름(매개변수);
}
public interface Animal {
    void makeSound();  // 추상 메서드 (자동으로 public abstract가 붙음)
    int MAX_AGE = 100; 
}

인터페이스의 메서드는 무조건 추상 메서드이므로 abstract 키워드를 생략해도 되고, 모든 필드는 자동으로 public static final 속성을 가지므로 static final을 명시하지 않아도 상수로 선언된다.


3. 인터페이스 구현하는 방법

인터페이스를 구현(implement) 하려면 implements 키워드를 사용한다.

public class Dog implements Animal {
    @Override
    public void makeSound() {
        System.out.println("멍멍!");
    }
    
    public void sleep(){
        System.out.println("새근새근 잡니다.");
    }
}

⚠️ 인터페이스를 구현하는 클래스는 반드시 모든 추상 메서드를 구현해야하기 때문에, 만약 구현하지 않았다면 해당 클래스도 abstract 클래스가 되어야한다.

abstract class AbstractDog implements Animal {
    // makeSound()를 구현하지 않았으므로 abstract class여야 함
}

⚠️Dog 클래스에서 메소드를 구현할 때 접근제어자를 public으로 했다. 즉, 오버하리딩할 때는 부모의 메서드보다 넓은 범위의 접근 제어자를 지정해야한다.


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

인터페이스는 구현체의 객체를 담을 수 있는 레퍼런스 타입으로 사용할 수 있다.
아래처럼 인터페이스를 활용하면 구현체를 변경하더라도 코드 수정 없이 사용 가능하므로 유연성이 증가한다. (Animal myDog를 바꾸지 않고 = new Cat()만 바꾸면 되기 때문)

public class Main {
    public static void main(String[] args) {
        Animal myDog = new Dog();  // 인터페이스 타입으로 객체 참조
        myDog.makeSound();         // "멍멍!" 출력
    }
}

myDog가 바라보고 있는 것은 Animal 인터페이스기 때문에 main클래스에서 dog.sleep()을 호출할 수 없다. 그래서 ((Dog)myDog).sleep()으로 캐스팅하여 사용해야한다. 반대로 Dog dog = new Animal(); 는 선언하지 못하고 컴파일 에러가 발생한다.


5. 인터페이스 상속

인터페이스도 다른 인터페이스를 상속할 수 있으며, 다중 상속이 가능하다.

public interface Pet {
    void beFriendly();
}

public interface DogInterface extends Animal, Pet {
    void guardHouse();
}

위와 같이 DogInterface는 Animal과 Pet 두 개의 인터페이스를 동시에 상속할 수 있다.
구현하는 클래스는 모든 인터페이스의 메서드를 구현해야한다.

public class GoldenRetriever implements DogInterface {
    @Override
    public void makeSound() {
        System.out.println("왈왈!");
    }

    @Override
    public void beFriendly() {
        System.out.println("꼬리를 흔들어요!");
    }

    @Override
    public void guardHouse() {
        System.out.println("집을 지켜요!");
    }
}

6. Java 8 이전 → Java 8 → Java 9 인터페이스 발전 과정

1️⃣ Java 8 이전: 추상 메서드만 있는 인터페이스 (모든 구현 강제!)

  • 인터페이스에서 메서드의 구현이 불가능했음
  • 즉, 모든 구현 클래스가 모든 메서드를 직접 구현해야 했음
// 인터페이스 (메서드 구현 불가능)
public interface Minji {
    void sayHello();  // 반드시 구현해야 함
    void introduce(); // 반드시 구현해야 함
}

// 첫 번째 구현 클래스
public class SeoMinji implements Minji {
    @Override
    public void sayHello() {
        System.out.println("안녕하세요! 저는 민지입니다.");
    }

    @Override
    public void introduce() {
        System.out.println("저는 백엔드 일짱입니다.");
    }
}

// 두 번째 구현 클래스
public class ParkMinji implements Minji {
    @Override
    public void sayHello() {
        System.out.println("안녕하세요! 저는 민지입니다."); //⚠️중복 코드 발생!
    }

    @Override
    public void introduce() {
        System.out.println("저는 백엔드 일짱입니다."); //⚠️중복 코드 발생!
    }
}

🚨 문제점
SeoMinji와 ParkMinji 클래스에서 같은 내용의 sayHello()introduce()메서드를 각각 구현해야 한다. 즉, 같은 기능을 하는 코드가 여러 번 반복되고 있다.
-> 중복 문제

2️⃣ Java 8: default method 도입 (인터페이스에서 일부 구현 가능!)

  • Java 8부터는 default method를 활용해 인터페이스에서 일부 메서드를 구현할 수 있음
    • default라는 키워드를 명시해야함
    • 이제 모든 구현 클래스가 같은 기능을 중복 구현할 필요 없음

왜 사용할까

바로 "하위 호환성"때문이다. 예를 들어 설명하자면, 여러분들이 만약 오픈 소스코드를 만들었다고 가정하자. 그 오픈소스가 엄청 유명해져서 전 세계 사람들이 다 사용하고 있는데, 인터페이스에 새로운 메소드를 만들어야 하는 상황이 발생했다. 자칫 잘못하면 내가 만든 오픈소스를 사용한 사람들은 전부 오류가 발생하고 수정을 해야 하는 일이 발생할 수도 있다. 이럴 때 사용하는 것이 바로 default 메소드다.

기존에 존재하던 인터페이스를 이용하여서 구현된 클래스를 만들고 사용하고 있는데,
인터페이스를 보완하는 과정에서 추가적으로 구현해야 할, 혹은 필수적으로 존재해야 할 메소드가 있다면, 이미 이 인터페이스를 구현한 클래스와의 호환성이 떨어지게 된다. 이러한 경우 default 메소드를 추가하게 된다면 하위 호환성은 유지되고 인터페이스의 보완을 진행 할 수 있다.

// 인터페이스 (Default Method 사용)
public interface Minji {
    //인터페이스에서 기본 동작 제공 -> 구현 클래스에서 굳이 구현할 필요 없음
    default void sayHello() {
        System.out.println("안녕하세요! 저는 민지입니다.");
    }

    default void introduce() {
        System.out.println("저는 백엔드 일짱입니다.");
    }
}

// 첫 번째 구현 클래스
public class SeoMinji implements Minji {
    //아무것도 오버라이딩하지 않아도 기본 메서드를 그대로 사용 가능!
}

// 두 번째 구현 클래스
public class ParkMinji implements Minji {
    //필요하면 메서드만 오버라이딩 가능
    @Override
    public void introduce() {
        System.out.println("저는 인프런을 공부하는 민지입니다!");
    }
}

// 실행 코드
public class Main {
    public static void main(String[] args) {
        SeoMinji minji1 = new SeoMinji();
        ParkMinji minji2 = new ParkMinji();

        minji1.sayHello();  //"안녕하세요! 저는 민지입니다."
        minji1.introduce(); //"저는 백엔드 일짱입니다."

        minji2.sayHello();  //"안녕하세요! 저는 민지입니다."
        minji2.introduce(); //"저는 인프런을 공부하는 민지입니다!"
    }
}

🔨개선된 점

  • introduce() 메서드는 인터페이스에서 기본 구현을 제공하므로, 구현 클래스에서 직접 구현할 필요 없음
  • 코드 중복이 줄어듦

🚨 여전히 문제점

  • 만약 System.out.println("좋아하는 것은 여행입니다 !");System.out.println("저는 백엔드 일짱입니다.");를 모두 포함하는 detailedIntroduce()라는 새로운 메서드를 추가해야 한다면?
  • introduce()와 동일한 코드 System.out.println("저는 백엔드 일짱입니다.");중복됨!
  • "백엔드 일짱" 대신 "백엔드 개발자"로 바꾸려면
    • introduce()detailedIntroduce() 둘 다 수정해야하고
    • 유지보수가 어렵고 실수로 하나만 수정하면 오류 발생 가능

3️⃣ Java 9: private method 추가 (코드 중복 제거 + 캡슐화)

  • Java 9부터 private method를 사용할 수 있게 되면서, 중복되는 로직을 별도로 관리 가능
  • 중복을 줄이면서 인터페이스 내부에서만 사용할 수 있도록 캡슐화 가능
//인터페이스 (Java 9부터 지원하는 private method)
public interface Minji {
    void sayHello(); //여전히 직접 구현해야 함

    default void introduce() {
        printBasicInfo();  // private method 호출 (중복 제거)
    }

    default void detailedIntroduce() {
        printBasicInfo();  // private method 호출 (중복 제거)
        System.out.println("좋아하는 것은 여행입니다 !");
    }

    private void printBasicInfo() {  // 인터페이스 내부에서만 사용 가능 (캡슐화)
        System.out.println("저는 백엔드 일짱입니다.");
    }
}

🔨개선된 점

  • printBasicInfo()라는 private method를 만들어 중복되는 부분을 한 곳에서 관리
  • introduce()detailedIntroduce()에서 중복 없이 같은 기능을 재사용할 수 있음
  • 만약 "백엔드 일짱" 대신 "백엔드 개발자"로 바꾸어도 printBasicInfo()만 수정하면 모든 메서드에 자동 반영됨
  • 캡슐화 유지private method이므로 인터페이스 내부에서만 사용할 수 있고, 구현 클래스에서는 접근 불가

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

Java 8부터 인터페이스에서도 static 메서드를 선언할 수 있다. 인스턴스 생성과 상관 없이 인터페이스 타입으로 호출하는 메소드이다. 이 메소드는 인터페이스 이름을 통해 직접 호출할 수 있다.

  • static 예약어를 사용하고 접근제어자는 항상 public이며 생략할 수 있다.
  • body가 있어야한다.
  • 오버라이드 할 수 없다.
public interface Animal {
    static void info() {
        System.out.println("동물 인터페이스입니다.");
    }
}
public class Main {
    public static void main(String[] args) {
        Animal.info();  // "동물 인터페이스입니다."
    }
}

위 코드처럼 static 메서드는 구현체를 만들지 않아도 interface이름.메소드로 호출할 수 있다.


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

Java 9부터 인터페이스 내에서 private 메서드를 정의할 수 있도록 했다.
이는 default 메서드와 static 메서드 내에서만 사용할 수 있는 헬퍼(보조) 메서드 역할을 한다.
private 메서드는 인터페이스 내부에서만 호출할 수 있으며, 외부에서는 호출할 수 없다.

👉 자바9 이전

interface SeoMinji {
    void sayHello(); // 직접 구현해야 함

    default void introduce() {
        printBasicInfo();  // private method 호출 (중복 제거)
    }

    default void detailedIntroduce() {
        printBasicInfo();  // private method 호출 (중복 제거)
        System.out.println("좋아하는 것은 여행입니다 !");
    }

    private void printBasicInfo() {  // 인터페이스 내부에서만 사용 가능 (캡슐화)
        System.out.println("저는 백엔드 일짱입니다.");
    }

    // 자바8 이전
    public static void weight() {
        System.out.println("제 몸무게는 xx입니다 ! 비밀입니다 비밀!!!");
    }
}

class 민지님베프 {
    public void 민지정보출력() {
        SeoMinji.weight(); // 민지님 : ?????????????????????????너가왜????????
    }
}

👉 자바9 이후
private 키워드 사용이 가능해지면서 인터페이스 내에서만 사용하는 메서드의 경우 private static을 통해 외부 접근을 막으면서 캡슐화가 가능해졌다!

interface SeoMinji {
    // 기존코드 생략...
    
    // 자바9 이후
    private static void weight() {
        System.out.println("제 몸무게는 xx입니다 ! 비밀입니다 비밀!!!");
    }
}

class 민지님베프 {
    public void 민지정보출력() {
        SeoMinji.weight(); // 컴파일에러 발생 ! 민지님 : 휴..
    }
}

(추가) 인스턴스 즉 객체에 의존하는 메서드면 static을 사용하지 않는게 좋고
의존하지 않는다면 private static으로 만들어서 재활용 하는게 좋다.


💡강한 결합과 느슨한 결합

  • 왼쪽의 그림을 보면 (강한 결합)

    • A는 B에 의존하고 있다. (A가 B를 사용)
    • 이 때, A가 C를 사용하게 하려면??
    • A는 B를 의존하고 있는 코드를 C를 의존하게끔 변경해야 한다.
  • 오른쪽 그림을 보면 (느슨한 결합)

    • A는 I 인터페이스에 의존하고 있고, I 인터페이스를 구현한 B를 사용한다.
    • 이 때, A가 C를 사용하게 하려면?
    • A는 I에 의존하고 있기 때문에, I 인터페이스를 구현한 C를 사용한다면, 따로 코드를 변경하지 않아도 된다.

0개의 댓글