티스토리 뷰

JAVA

[JAVA] 인터페이스(Interface) 핵심 이해하기

까리한 새우 2024. 3. 18. 19:03

인터페이스(Interface)란

인터페이스는 프로그램 내 다양한 기능을 하는 클래스들에게 기본이 되는 틀(구조)를 제공하는 역할을 한다.

이전의 포스팅을 보았다면 알겠지만, 추상 클래스와 비슷한 개념이라고 생각하면 된다.

 

하지만, 자바는 단일 상속을 원칙으로 하고 있기 때문에, 다중 상속을 지원하기 위해 인터페이스를 사용할 수 있다.

자바에서 비슷한 개념인 추상 클래스는 추상 메서드뿐만 아니라 생성자, 필드, 일반 메서드도 포함할 수 있습니다.

하지만, 인터페이스는 오로지 상수(final)와 추상 메서드만 포함할 수 있습니다.

 

 

인터페이스 특징

1. 다중 상속 가능하다.

인터페이스는 껍데기만 존재하기 때문에 클래스 상속 시 발생했던 모호함이 없다. 

그렇기 때문에 다중 상속이 가능하다.

 

추상 클래스를 상속할 때 사용하던 extends 가 아닌 implements 를 사용하여 인터페이스를 상속받을 수 있다.

 

class Cat implements Animals, Pet

 

 

2. 추상 메서드와 상수만 사용 가능하다.

인터페이스에는 구현 코드를 작성할 수 없다.

interface Animal {
    public static final int a = 3; //필드는 상수
    public abstract void move();   //추상 메서드만 존재
}

 

 

3. 생성자를 생성할 수 없다.

인터페이스는 객체가 아니므로 생성자를 사용할 수 없다. 

추상 클래스와 마찬가지로 인터페이스에서는 객체를 생성할 수 없으며 자식 클래스를 통해 구현해야 한다.

(추상 클래스와 마찬가지로 익명 이너 클래스로도 구현이 가능하다.)

 

 

4. 메서드 오버라이딩

자식 클래스는 부모 인터페이스의 추상 메서드를 모두 오버라이딩 해야한다.

 


 

인터페이스 구현

자바에서 인터페이스를 선언할 때는 interface 라는 키워드를 붙여서 만들면 된다.

우리가 클래스를 생성할 때 class 키워드를 붙여주듯이 class 대신 interface 를 붙여주면 된다.

추상 클래스와 달리 implements 라는 키워드를 통해 상속을 받을 수 있다.

 

그리고 위에서 설명했듯이 인터페이스에는 구체적인 대상을 생성할 수 없으며 오직 상수와 추상 메서드만 사용할 수 있다.

아래 추상 메서드는 껍데기만 생성하고 자신을 상속하는 자식 클래스에서 오버라이딩하여 사용한다.

public interface Animal {
    // static final 을 통해 상수 선언
    public static final String name = "동물";
    
    // abstract 를 통해 추상 메서드 선언
    public abstract void move();
    public abstract void cry();

 

아래 코드에서 Animal 인터페이스를 구현하는 Dog, Cat 클래스를 확인해 보자.

public class Dog implements Animal {
    @Override
    public void move() {
        System.out.println("강아지가 움직입니다.");
    }

    @Override
    public void cry() {
        System.out.println("멍멍");
    }
}
public class Cat implements Animal {
    @Override
    public void move() {
        System.out.println("고양이가 움직입니다.");
    }

    @Override
    public void cry() {
        System.out.println("야옹~");
    }
}

 

강아지와 고양이 클래스를 Animal 인터페이스를 상속받게 하고, 추상 메서드들을 구현하였다.

결과를 보면 각 클래스에서 구현한 메서드의 결과가 출력되는 것을 확인할 수 있다링

 

< 코드 >

 

public class interfaceTest {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Cat cat = new Cat();

        dog.move();
        dog.cry();
        cat.move();
        cat.cry();
    }
}

 

< 결과 >

 

 

그리고 다음과 같이 'public static final' 과 'public abstract' 는 생략이 가능하다.

생략을 한 상태를 주로 쓰는 지는 모르지만 내 개인적인 생각으로는 명시해주는 것이 좋은거 같다.

코드를 한눈에 보고 쉽게 이해할 수 있기 때문이다.

 

 

그리고 잠깐 짚고 넘어갈 내용 이 있다.

인터페이스끼리의 상속에 관한 내용인데, 우리가 추상 클래스에서 했듯이 클래스와 클래스 사이의 상속은 extends 키워드를 사용하여 상속하는 것이 맞다. 그리고 이 글에서 공부했듯이 클래스에서 인터페이스를 상속할 때는 implements 키워드를 사용하여 상속을 받는다.

인터페이스와 인터페이스끼리의 상속을 통해 확장을 해야할 때는 extends 키워드를 사용한다.

 

인터페이스 다중 상속

다음 코드는 인터페이스의 다중 상속 코드이다.

 

< 코드 >

interface Animal { public abstract void cry(); }
interface Pet { public abstract void play(); }

class Dog implements Animal, Pet {
    public void cry() {
        System.out.println("멍멍");
    }
    public void play() {
        System.out.println("강아지가 놀고 있습니다");
    }
}

class Cat implements Animal, Pet {
    public void cry() {
        System.out.println("야옹");
    }
    public void play() {
        System.out.println("고양이가 놀고 있습니다");
    }
}

public class interfaceTest {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Cat cat = new Cat();
        
        dog.cry();
        dog.play();
        cat.cry();
        cat.play();
    }
}

 

< 결과 >

 

 


 

디폴트 메서드 & 정적 메서드

지금 배운 인터페이스는 상수와 추상 메서드만 존재할 수 있다고 했다.

하지만 자바8 버전부터는 디폴트 메서드와 정적 메서드를 사용할 수 있다.

 

디폴트(default) 메서드를 사용하는 이유는?

바로 '하위 호환성' 때문이다.

기존 인터페이스를 확장해서 새로운 기능을 추가하기 위해서 추상 메서드를 하나 더 정의를 한다고 한다면, 기존에 인터페이스를 구현하고 있는 모든 코드들에 추상 메서드를 추가해야 하기 때문이다.

디폴트 메서드를 정의하면 기존에 사용하고 있던 코드들에서는 오버라이딩을 하지 않아도 된다.

 

그리고 정적(static) 메서드는 뭐 대충 여러가지 제약이 있어 자바에서 허용하지 않았다가, 버전 업이 되면서 추가되었다.

정적 메서드는 대충 이정도로만 알고 있으면 될 것 같다.

 

default 메서드에 대해 좀 더 살펴보자

  • 디폴트 메서드는 앞에 키워드 default 를 붙이며 일반 메서드처럼 구현부 {...} 가 있어야 한다.
  • 디폴트 메서드 역시 접근제어자가 public 이며 생략 가능하다.
  • 자식 클래스(구현체)에서 default 메서드를 오버라이딩 하여 재정의가 가능하다. (필수는 아니다)
  • 인터페이스는 Object 클래스를 상속받지 않기 때문에, Object 클래스가 제공하는 기능인 equals(), hasCode() 등은 기본 메서드로 제공하지 못한다. 따라서 관련된 기능을 사용하고 싶다면 자식클래스에서 직접 재정의를 해주어야 한다.

< 코드 >

interface Game {
    public abstract void play1();
	public abstract void play2();
    
    // default로 선언함으로 메서드를 구현
    default void event() { System.out.println("새로운 이벤트 시작"); }
}

// Game 인터페이스를 구현한 mobileGame 클래스
class MobileGame implements Game {
    //추상 메서드만 구현
    public void play1() {
        System.out.println("1번 컨텐츠 시작");
    }
    public void play2() {
    	System.out.println("2번 컨텐츠 시작");
    }
}

public class Main {
    public static void main(String[] args) {
        MobileGame mg = new MobileGame();
        
        //인터페이스 타입으로 업캐스팅
        Game game = (Game)mg; //괄호는 생략해도 됨
        
        //인스턴스의 인터페이스 디폴트 메서드 호출
        game.event();
        game.play1();
        game.play2();
    }
}

 

자식클래스에서 인터페이스의 디폴트 메서드를 호출하기 위해선, 객체의 타입이 반드시 인터페이스 타입으로 업캐스팅 해주어야 한다.

 

< 결과 > 

 


 

인터페이스의 장점

인터페이스를 사용하면 다중 상속이 가능할 뿐만 아니라 다음과 같은 장점을 가질 수 있다.

 

  1. 인터페이스는 메서드의 틀(구조)을 미리 만들어 개발자 간의 의사소통을 원할하게 해준다.
  2. 대규모 프로젝트 개발 시 일관되고 정형화된 개발을 위한 표준화가 가능하다.
  3. 클래스의 작성과 인터페이스의 구현을 동시에 진행할 수 있으므로, 개발 시간을 단축할 수 있다.
  4. 클래스와 클래스 간의 관계를 인터페이스로 연결하면, 클래스마다 독립적이 프로그래밍이 가능하다.

 

참고(출처)

https://www.tcpschool.com/java/java_polymorphism_interface

https://coding-factory.tistory.com/867

https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4Interface%EC%9D%98-%EC%A0%95%EC%84%9D-%ED%83%84%ED%83%84%ED%95%98%EA%B2%8C-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC

https://hyunki99.tistory.com/12