프로그래밍/OOP_Pattern_TDD

인터페이스 (Interface)

라이프노트 2025. 5. 29. 16:56

인터페이스 (Interface)

  • 클래스 사용의 권고사항을 제시하는 역할을 하는 특수형태의 클래스
  • 아무런 구현이 되어 있지 않으며, 모든 메소드가 추상 메소드이다. (Java 1.8에서 변경됨)
  • be able to ~ , ~할수있는에 해당하는 기능을 부여하며 ~able로 네이밍하는경우가 많다.

인터페이스의 특징

  • class 가 아닌 interface 키워드를 사용한다.
    • public과 default 접근제어자 사용가능하다.

  • 멤버 변수는 항상 "public static final"이다.
    • 인터페이에는 클래스 멤버 변수(static) 밖에 생성하지 못한다.
    • 인터페이스는 객체를 생성하지 않기 때문에 객체 멤버 변수가 없다.
      • 객체를 생성할 수 없기 때문에 생성자도 없다.
    • "public static final" 키워드는 생략 가능 하다.
  • 멤버 메소드도 항상 "public abstract"이다.  (Java 1.8에서 default 메소드 허용됨)
    • 정의상으로 모든 메서드가 추상메서드인 클래스이기 때문에 어찌며보면 당연한 이야기
    • 오버라이딩을 위한 메서드이기 때문에 private 메서드는 의미가 없다
    • "public abstract"는 생략 가능하다
  • 정적 메소드도 선언이 가능하다.
    • 정적 메소드는 추상메서드가 될 수 없다. (static abstract X)
      • 정적 메소드는 구현부가 있어야 한다.
    • 정적 메소드는 오버라이딩 할 수 없다.
public interface Idoable {
  public static void staticDo() {
    // do something;
  }
  
  void do();  // public abstract 생략 가능
}

public class DoSomething implements IDoable {

  @override 
  public void do() {
    //do something2;
  }
}

public class Test {
  public static void main(String[] args) {
    Idoable.staticDo();	//인터페이스의 static 메소드 호출 방법
    
    Idoable iDo = new DoSomething();
    iDo.do();
  }
}
  • 인터페이스의 상속은 implements 키워드 사용한다.
  • 클래스는 하나만 상속할 수 있으나, 인터페이스는 여러 개를 구현 가능하다.
    • extends 에는 하나의 클래스만 적을 수 있지만, 인터페이스는 implements 하고 여러 개의 인터페이스를 적을 수 있음
      • 이때 구상클래스(구현 클래스)에서는 인터페이스에 정의된 메소드를 모두 구현하여야 한다.
    • 인터페이스를 이용하면 다중 상속을 한 것 처럼 사용할 수 있다.
  • 인터페이스명을 자료형으로 하여 구현체의 객체를 참조할 수 있다.
    • 상속관계의 경우와는 다르게 인터페이스의 객체는 생성되지 않는다.    new Interface(); // 불가
    • 그러나 메서드는 오버라이딩 하기 때문에 가상 메서드 호출은 일어난다.
    • 이를 통하여 다형성을 구현할 수 있다.
      • 오버라이딩 overriding : 하위 클래스에서 상위 클래스의 메소드를 재정의 하는 것
      • 오버로딩 overloading : 이름은 동일하지만 매개변수가 다른 경우
  • 추상메서드가 없다면 구현체에서 메서드 구현을 안해도 된다.
    • 구현해야 할 메서드가 하나도 정의되지 않은 경우
    • default 메소드만 존재하는 경우
  • 인터페이스는 객체를 생성할 수 없다.
interface IFoo{
    public static final int MEMBER_VAR = 10; // (상수는 대문자와 '_'조합)
                                            // public static final 생략 가능

    public abstract void methodA(int param); // public abstract 생략가능
    void methodB(int param); // public abstract
}

class Foo implements IFoo{

    @Override
    public void methodA(int param) {
        System.out.println(param);
    }

    @Override
    public void methodB(int param) {
        System.out.println(param);
    }
}
  • 인터페이스의 이름의 예시
    • (1)interface Ifoo < class Foo
      • 상속하는 자식에서 부모쪽으로 화살표 한다
    • (2)interface Printable <- class Bar
    • (1)과 같이 인터페이스명에 대문자 I를 붙여서 구분한다.
      • 혹은 (2)와 같이 형용사로 표현하기 좋은 이름(~able)은 I를 생략한다.

인터페이스 간의 상속

  • 인터페이스로 인터페이스를 상속할 경우, extends를 사용한다.
    • 인터페이스를 구현 할때만 implements를 사용한다.
    • 클래스 <- 클래스 상속과 달리 다중 상속 가능하다.
  • 인터페이스의 상속으로 다형성을 구현할 수 있다.
  • 인터페이스를 상속한 인터페이스를 구현할 때는 상속받은 모든 부모 인터페이스의 메서드를 구현해야한다.
  
interface Walkable{
    void walk();
}

interface Runnable{
    void run();
}

interface Jumpable extends Walkable, Runnable{
    void jump();// walk(), run()도 포함되어 있을 것
}

class Jumper implements Jumpable{

    @Override
    public void walk() {
        System.out.println("walk");
    }

    @Override
    public void run() {
        System.out.println("run");
    }

    @Override
    public void jump() {
        System.out.println("jump");
    }
}

 

 

JDK 1.8 이후의 인터페이스

  • JDK 1.8 이후 인터페이스에서는 default 메소드를 구현할 수 있다.
  • default 메서드는 모든 하위 클래스에 필수이며, 중복되는 메서드로 중복 구현을 방지하는 역할을 한다.
    • 인터페이스의 철학(정의)과는 맞지 않으나, 모든 자식 메서드가 동일하게 구현되어야 하는 메소드가 생긴 경우 쉽게 기능을 추가하기위해 만들어졌다.
  • default 메서드 특징
    • default 메소드는 항상 public이다.
      • default는 접근제어자의 default가 아니고 인터페이스의 default 메서드를 나타낸다.
      • 인터페이스를 구현하는 모든 클래스에서 사용 될 메서드이기 때문에 private은 의미가 없다.
    • default 메소드는 구현체에서는 반드시 구현할 필요는 없다.
      • abstract 메서드의 경우 구현체에서 반드시 구현하여야 하지만 default 메서드의 경우 구현하지 않아도 된다.
      • 자녀 클래스에서 구현하지 않은 default 메서드에 접근시 부모클래스의 default 메서드에 접근된다.
interface IFooTwo {
    void abstractMethod();
    default void defaultMethod(){ // 디폴트 메소드, 구현 클래스에서 오버라이드 가능
        System.out.println("Default method"); // 구현을 해주어야함
    }
}


class FooTwo implements IFooTwo{

    @Override
    public void abstractMethod() {

    }
    @Override // Overriding not necessary
    public void defaultMethod(){
        System.out.println("Overriden default method");
    }
}

class SuperFoo{
   public void defaultMethod(){
        System.out.println("Default method in Super class");
    }
}

class FooThree extends SuperFoo implements IFooTwo{

}

// 인터페이스의 static 메소드
interface IBar {
    static void staticMethod(){
        System.out.println("static method"); //바로 구현 가능
    }
}

 

  • 상속받은 클래스와 구현한 인터페이스의 모두 default method가 있다면 상속받은 부모클래스의 default method가 실행된다.
    • 이때 인터페이스의 default method는 실행되지 않는다.
    • 부모와 인터페이스에 모두 동일한 메서드가 있는 경우 부모클래스에 있는 메소드를 실행한다.
    • 그렇다면 두 개 이상의 인터페이스에 동일한 default method가 있다면 구현 클래스에서 default method는 뭐가 호출될까???
  • 인터페이스의 정적 메서드가 있는경우
    • 인터페이스 명으로 호출 가능 ex ) IBar.staticMethod();
    • 구현한 클래스명으로 호출 불가능 ex) // Bar.staticMethod();
      • 상속에서는 가능하지만 인터페이스에서는 불가능 하다.

인터페이스와 추상메서드의 차이점

  • 추상메서드는 멤버 변수가 있기 때문에 자녀 클래스들에 공통 속성을 설정할 수 있다. 또한 일반메서드와 추상메서드로 공통의 기능도 구현할 수 있다. 그래서 추상메서드는 어떤 개념에 상위 개념으로 세분화 되는 하위 클래스들을 대표하는 클래스로 사용할 수 있다.
    • 대표적인 예로 Animal 이란 큰 대분류안에 많은 종류의 Animal 을 상속하는 동물클래스들을 작성할 수 있으며 그들의 공통 속성인 name, lifespan, color 것을 공통적으로 상속해 줄 수 있고 또한 공통 기능인 walk(),eat()등의 기능들도 상속해 줄 수 있다.
    • 추상메서드의 역할을 is kind of(어떤 종류의) 라고 하나보다..
  • 반면에 인터페이스는 추상메서드 밖에 없지만 다중 상속이 가능하기 때문에 세분화된 기능을 나타내는데 활용될 수 있다.
    • 예를 들면 Animal의 하위클래스들 중에 세분화 기능이라고 볼 수 있는 flyable, ridable 등을 표시해줄 수 있으며 동물별로 여러개의 세분화 된 기능들을 추가하는데 사용할 수 있다
    • 그래서 인터페이스의 역할을 able to(~를 할 수 있는)라고 하나보다..
  • 부모 속성(또는 공통)의 멤버 변수가 반드시 필요한 경우라면 추상 클래스를 이용하고, 처리할 기능(able to)을 하나의 그룹으로 모아야 하는 경우에는 interface로 선언한다.
  • Iterator 인터페이스 실제 예
public interface Iterator<E> {
    boolean hasNext();

    E next();

    default void remove() {
        throw new UnsupportedOperationException("remove");
    }
}
// 직접 구현하는 방법
import java.util.Iterator;
public class PancakeHouseMenu implements Iterator<MenuItem> {
  MenuItem[] items;
  int position = 0;
  
  public PancakeHouseMenu() {
    items = new MenuItem[3];
    
    MenuItem bread = new MenuItem("Bread");
    MenuItem soup = new MenuItem("soup");
    MenuItem juice = new MenuItem("juice");
    
    items[0] = bread;
    items[1] = soup;
    items[2] = juice;
  }

  public boolean hasNext() {
    if(position >= menuItems.length || items[position] == null)
      return false;
    else
      return true;
  }
  
  public MenuItem next() {
    MenuItem item = items[position];
    position++;
    return menuItem;
  }
  
  //not necessary
  //public void remove() {}
}


// 간단한 방법
public class PancakeHouseMenu {

  List<MenuItem> menuItems;
  
  public PancakeHouseMenu() {
    menuItems = new ArrayList<MenuItem>();
    
    MenuItem bread = new MenuItem("Bread");
    MenuItem soup = new MenuItem("soup");
    MenuItem juice = new MenuItem("juice");
    
    menuItems.add(bread);
    menuItems.add(soup);
    menuItems.add(juice);
  }

  public Iterator<MenuItem> createIterator() {
    return menuItems.iterator();
  }
}
//java.util.ArrayList 발췌

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
... 생략 ...

    transient Object[] elementData;  //transient : serialize 제외할 멤버변수 선언

    public Iterator<E> iterator() {
        return new Itr();
    }

    /**
     * An optimized version of AbstractList.Itr
     */
    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        // prevent creating a synthetic constructor
        Itr() {}

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }
...
}