인터페이스란?

인터페이스는 일종의 추상클래스이다.

인터페이스는 추상클래스보다 추상화 정도가 높다.

그래서 오직 추상메서드상수만을 멤버로 가질 수 있다.

추상클래스가 미완성 설계도이면 인터페이스는 그냥 설계도이다.

인터페이스의 작성법

인터페이스를 작성하기 위해서는 다음과 같이 작성한다.

interface 인터페이스이름{
        public static final 타입 상수이름 = 값;
        public abstract 메서드이름(매개변수);
}

인터페이스의 이러한 작성법을 본다면 특이한 사항이 있다.

인터페이스를 작성하기 위해서는 지켜야할 제약사항이 존재한다.

  • 모든 멤버변수는 public static final이어야 하며 이를 생략할 수는 있다.
  • 모든 메서드는 public abstract이어야 하며 이를 생략할 수 있다.
  • static메서드와 default메서드는 JDK1.8부터 예외로 한다.

만약 이러한 public static final이나 public abstract를 생략을 한다면 알아서 컴파일러가 컴파일시 자동으로 추가해 준다.

인터페이스의 특징들

인터페이스의 상속

인터페이스는 인터페이스 끼리만 상속이 가능하다. 또한, 클래스와 달리 다중상속을 허용한다.

또한, 인터페이스는 클래스와 달리 Object같은 최고 부모가 존재하지 않는다.

인터페이스의 구현

인터페이스를 클래스에 사용할 때 구현이라는 말이 쓰인다.

인터페이스는 추상클래스와 비슷하다고 하지 않았는가?

그렇기 때문에 추성클래스와 비슷하게 인스턴스를 그 자체로 생성이 불가능하다.

클래스 간의 상속은 extends확장이라는 의미로 사용하지만

인터페이스는 달리 이러한 설계도를 완성을 해야한다는 의미이기 때문에 구현이라는 의미로 implements를 사용하여 클래스를 사용한다.

class 클래스이름 implements 인터페이스 이름{

}

//예시
class Dog implements Animal{
        /...
}

인터페이스를 implements한다면 이 인터페이스에 있는 모든 메서드를 구현해야 한다.

하지만 일부 메서드만 구현하고 싶을때도 있다. 그럴 때는 abstract를 붙여서 추상클래스로 만들면 가능하다.

abstract class Dog implements Animal{
        /...
}

지금까지 인터페이스구현이라는 단어를 사용하고 클래스확장이라는 단어를 사용하기 때문에

단어의 의미가 달라서 인터페이스를 사용하면 부모가 없다고 생각할 수도 있지만 그것은 아니다.

인터페이스로 부터 상속받은 추상메서드를 구현하는 것이기 때문에 지금까지 사용했던 부모 라는 의미와는 100% 같지는 않지만 조금 다른의미의 조상이라고 볼 수 있다.

접근제어자의 사용

다음과 같은 class와 인터페이스가 있다.

interface Animal{
    void feed(); // public abstract void feed();
}

class Dog implements Animal{
        public void feed(){

        }
}

이때 Dog클래스에서 feed()메서드를 사용하기 위해서는 반드시 public으로 선언을 해야 사용할 수 있다.

왜냐하면 오버라이딩 할때는 부모의 메서드보다 넓은 범위의 접근 제어자를 지정해야하기 때문에 public abstract보다 넓은 범위를 사용하기 위해서 public을 반드시 사용해야 한다.

인터페이스를 사용한 다중상속

자바에서는 다중 상속을 금지하는 이유는 만약 두 부모로 부터 상속받는 멤버변수중 겹치는 것이있다면 컴파일러는 어떤 조상의 것을 상속받아서 사용했는지 알수가 없기 때문에 이를 금지하였다.

하지만 인터페이스는 다중 상속이 된다는것이 이상하지 않는가?

왜냐하면 인터페이스static상수만 정의할 수 있기 떄문에 부모클래스의 멤버변수와 충돌하는 겨우는 거의 없다.

그리고 static상수는 보통 클래스 이름을 붙여서 사용하기 때문에 충돌되더라도 괜찮다.

만약, 메서드가 같아서 걱정이라면 당연히 부모 클래스의 메서드를 상속받으면되기 때문에 문제가 없다.

이렇게 인터페이스는 부모클래스와 충돌하지 않기 때문에 이를 이용하여 단일상속만 가능한 자바에서 다중상속을 구현할 수 있다.

다음과 같은 예제를 살펴보자

public class Apple{
    protected int sweet;
    protected int growLevel;    

    public void feed(){
        growLevel++;
    }

public class Dessert{
        protected int price;

        public void eat(){
            //.. 먹는것을 구현
        }
        public int getPrice(){
            return price;
        }
        public void setPrice(int price){
            this.price = price;
        }
    }
}

이러한 Apple이라는 클래스와 Dessert라는 클래스를 다중상속하여 AppleDessert라는 클래스를 만들고 싶을 떄 단일상속을 원칙으로 하는 자바에서는 구현을 못할 것이라고 생각하지만

인터페이스를 사용하여 구현할 수 있다. 다음과 같은 인터페이스를 구현한다.

public interface InterfaceAppleDessert{
        public abstract void eat(); 
        public abstract int getPrice();
        public abstract void setPrice(int price);
} 

InterfaceAppleDessert라는 인터페이스는 Dessert클래스에 정의된 메서드와 일치하는 추상메서드를 가지는 인터페이스다 이를 이용하여 실제로 만들 AppleDessert라는 클래스를 구현해보자

public class AppleDessert extends Apple implements InterfaceAppleDessert{
        Dessert des = new Dessert();
        public void eat(){
            des.eat();
        }        
        public int getPrice(){
            return des.getPrice();
        }
        public void setPrice(int price){
            desc.setPrice(price);
        }
}

이렇게 InterfaceAppleDessert인터페이스 때문에 메서드를 반드시 오버라이딩 할 수 밖에 없다.

이러한 메서드를 Dessert인스턴스를 만들어 Dessert 메서드를 각각의 오버라이딩된 메서드에 호출하면 다중상속을 구현할 수 있다.

인터페이스를 이용한 다형성

다형성에서는 자식클래스의 인스턴스를 부모타입의 참조변수로 참조가 가능하다는 것을 배웠다.

인터페이스 또한, 이를 구현한 클래스의 부모라고도 볼 수 있기 때문에 이를 형변환하거나

인터페이스 타입의 참조변수로 이를 구현한 클래스의 인스턴스를 참조할 수 있다.

이전 예시를 찾아보자 Dog클래스와 Animal인터페이스를 살펴보면 다음과 같다.

// 예시 1번
Animal animal = (Animal)new Dog();

//예시 2번
Aniaml animal = new Dog();

또한, 클래스의 다형성과 같이 매개변수와 리턴타입에도 사용이 가능하다.

void bark(Animal animal){
    //...
}

Animal method(){
        Dog dog = new Dog();
        return dog;
}

리턴타입이 인터페이스이면 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것을 의미한다.

분명 method()의 반환타입은 Animal이라는 인터페이스인데 implementsDog클래스의 인스턴스가 반환되는 것을 볼 수 있다.

인터페이스의 장점

  • 개발시간을 단축시킬 수 있다.
    • 인터페이스가 작성되면 여러명이서 각각 인터페이스를 구현할 수 있다. 예를들어서 Animal이라는 인터페이스를 개발해놓으면 2명이서 Dog클래스를 만들필요없이 분담하여 Cat클래스도 만들 수 있는 것이다.
  • 표준화가 가능하다
    • 프로젝트의 기본틀을 인터페이스로 작성하여 다른 개발자들이 개발할 떄 또한, 일관되고 정형화된 프로그램의 개발이 가능하다.
  • 서로 관계없는 클래스들에게 관계를 맺어 줄 수있다.
    • 아무런 관계를 가지지 않고 부모클래스도 다른 클래스에게 여러개를 implements가 가능한 인터페이스를 사용하여 하나의 인터페이스를 공통적으로 구현하도록 함으로써 관계를 맺어 줄 수 있다.
  • 독립적인 프로그래밍이 가능하다
    • 인터페이슨느 클래스의 선언과 구현을 분리시킬 수 있기 때문에 실제구현에 독립적인 프로그램을 작성하는 것이 가능하다. 클래스와 클래스의 직접적인 관계를 인터페이스를 이용하여 간접적인 관계로 변경하면, 한 클래스의 변경이 관련된 다른 클래스에 영향을 미치지 않는 독립적인 프로그래밍이 가능하다.

인터페이스의 이해

인터페이스를 이해하기 위해서는 다음과 같은 두가지 사항을 염두해둬야한다.

  • 클래스를 사용하는 쪽과 클래스를 제공하는 쪽이 있다.
  • 메서드를 사용하는 쪽에서는 사용하려는 메서드의 선언부만 알면 된다.

다음과 같은 예시를 살펴보자

class A {
    public void methodA(B b){
        b.methodB();
    }
}

class B{
    public void methodB(){
        System.out.println("this is method B");
    }
}

public static void main(String[] args) {
        A a = new A();
        a.methodA(new B());
}

현재상황은 A클래스가 B의 인스턴스를 생성하고 메서드를 호출한다.

이때 A를 사용하는 쪽이라하고 B를 제공하는 쪽이라하자.

이렇게 직접적으로 연결되어 있다면 만약 B라는 클래스가 변경이된다면 예를들어 methodB()가 사라지거나 혹은 B라는 클래스가 변경이되면 A라는 클래서의 메서드도 변경을 반드시 해야한다.

이렇게 직접적으로 관계되어있는 둘의 관계를 바꾸기 위해서는 인터페이스를 사용하여 클래스의 선언과 구현을 분리해야한다. 다음과 같은 인터페이스를 생성해보자

interface I {
    public abstract void methodB();
}

class B implements I{
    System.out.println("this is method B");
}

class A{
        public void methodA(I i){
            i.methodB();
        }
}

이런식으로 인터페이스 I를 만들어서 B의 클래스를 implements하였다.

선언은 인터페이스가 맡아서 하였기 때문에 내가 만약에 이제 클래스 B를 쓰지 않는다고 해도

C라는 클래스를 생성하여 대체한다고 하여도 implementsC클래스에 한다면 문제가 없어진다.

혹은 다음과 같은 예제를 보자

interface PayInterface{
    public abstract void paymentMethod(); 
}

class Card implements PayMethod{
    public void paymentMethod(){
        System.out.println("카드로 결제하겠습니다.");
    }
}

class Cash implements PayMethod{
    public void paymentMethod(){
        System.out.println("현금으로 결제하겠습니다.");
    }
}

class PayService(){
    public void buy(PayInterface i){
            i.paymentMethod();
    }
}

public static void main(Stirng[] args){
    PayService service = new PayService();

    service.buy(new Card()); //카드로 결제하겠습니다.
     service.buy(new Cash()); //현금으로 결제하겠습니다.
}

인터페이스를 사용하여 내가 원하는 방식대로 인터페이스만 implements한다면 사이트에서 결제방식이 바뀌더라도 직접적으로 PayService와 결제방식 클래스를 둘다 안바꾸어도 되고 그냥 PayInterfaceimplements한 하나의 클래스만 바로 생성하면 바로 쓸수있다.

디폴트 메서드와 static메서드

이부분은 앞선 설명으로 알듯이 JDK1.8버전 이후에 생긴 것이다.

원래는 인터페이스는 추상 메서드만 선언이 가능하다.

java.util.Collection 인터페이스는 static메서드를 선언을 할 수 없었어서 Collections라는 킆래스에 들어가게 되었다.

static메서드는 많이 들어봤지만 디폴트 메서드는 처음일 것이다.

디폴트 메서드는 추상 메서드의 기본적인 구현을 제공하는 메서드이 이다.

추상 메서드가 아니기 때문에 디폴트 메서드가 새로 추가된 해당 인터페이스를 구현한 클래스를 변경하지 않아도 된다. 굳이 메서드를 오버라이딩 할 필요가 없기 때문이다.

// 선언방법
default void newMethod() {}

앞에 default를 붙이면된다.

디폴트 메서드는 추상 메서드와 달리 ;만 붙여서 끝나는 것이 아닌 {}부분이 필요하다.

하지만 이러한 디폴트 메서드도 구현하다 보면 만약에 기존 메서드와 이름이 충돌되는 경우가 있을 것이다. 이럴 때는 다음과 같은 규칙을 지켜야한다.

  1. 여러 인터페이스의 디폴트 메서드 간의 충돌
    • 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩해야 한다.
  2. 디폴트 메서드와 부모 클래스의 메서드 간의 충돌
    • 부모 클래스의 메서드가 상속되고, 디폴트 메서드는 무시된다.

그냥 편하게 하려면 디폴트 메서드라도 오버라이딩을 하면 그만이다.

Reference

  • 자바의 정석

'Java > Java' 카테고리의 다른 글

[Java/Study] 예외처리  (0) 2021.09.14
[Java/Study] 내부 클래스  (0) 2021.09.11
[Java/Study] 추상클래스  (0) 2021.09.11
[Java/Study] 다형성  (0) 2021.09.10
[Java/Study] 제어자  (0) 2021.09.09

+ Recent posts