Yoon DevLog
article thumbnail

 

 

인터페이스(Interface)

자식 클래스가 여러 부모 클래스를 상속받을 수 있다면, 다양한 동작을 수행할 수 있다는 장점을 가질 수 있다. 

하지만 클래스를 이용해 다중 상속을 할 경우 메소드 출처의 모호성 등 여러가지 문제가 발생할 수 있어 자바에서는 클래스를 통해서 다중 상속은 할 수 없다!

 

하지만 다중 상속의 이점을 버릴 수 없기에 인터페이스를 통해 다중 상속을 지원하고 있다.

인터페이스(Interface)란 다른 클래스를 작성할 때 기본이 되는 틀을 제공하면서, 다른 클래스 사이의 중간 매개 역할까지 담당하는 일종의 추상 클래스를 의미한다.

 

인터페이스(Interface)는 오로지 추상 메소드와 상수만을 포함할 수 있다!

이 부분이 추상 클래스와 차이가 나는데, 그 부분은 다음 게시글로 정리할 예정이다.

 

[JAVA] 추상클래스(Abstract Class) 총 정리

추상클래스(abstract class) 추상 클래스를 처음 접하면 무엇을 추상화한다는 것인가? 추상? 애매한 이건 뭐지?? 라는 생각이 들 것이다. 먼저 추상의 사전적 정의는 아래와 같다. 추상 - 여러가지 사

y-it.tistory.com

 

 

인터페이스(Interface)의 선언

자바에서 인터페이스 선언할 때에는 접근 제어자와 함께 interface 키워드를 사용하면 된다.

접근제어자 interface 인터페이스이름 {
	public static final 타입 상수이름 = 값;
    
    public abstract 메소드이름(매개변수목록);
    
}

클래스와는 달리 인터페이스의 모든 필드는 public static final이어야 하며, 모든 메소드는 public abstract이어야 한다.

하지만 이 부분은 모든 인터페이스에 공통으로 적용되는 부분이므로 제어자는 생략할 수 있다.

생략된 제어자는 컴파일 시 자바 컴파일러가 자동으로 추가해준다!

 

 

인터페이스 구현

인터페이스는 추상 클래스와 마찬가지로 자신이 직접 인스턴스를 생성할 수는 없다. 

따라서 인터페이스가 포함하고 있는 추상 메소드를 구현해 줄 클래스를 작성해야만 한다!

class 클래스이름 implements 인터페이스이름 { ... } 
// 여기서 implements는 '구현'의 뜻을 가진다
interface Animal { public abstract void cry(); }  // 인터페이스 선언

class Cat implements Animal { // 인터페이스 구현
    public void cry() {
        System.out.println("냐옹냐옹!");
    }
}

class Dog implements Animal { // 인터페이스 구현
    public void cry() {
        System.out.println("멍멍!");
    }
} 

public class Polymorphism03 {
    public static void main(String[] args) {
        Cat c = new Cat();
        Dog d = new Dog(); 

        c.cry(); // 냐옹냐옹! 출력
        d.cry(); // 멍멍! 출력
    }
}

 

 

인터페이스 일부 구현

추가로 만약 모든 추상 메소드를 구현하지 않는다면, abstract 키워드를 사용하여 추상 클래스로 선언해야 한다.

interface Animal {
    void walk();
    void run();
    void breed();
}

// Animal 인터페이스를 일부만 구현하는 추상 클래스
abstract class Mammalia implements Animal { // abstract를 붙여 추상클래스로 선언
    public void walk() { ... }
    public void run() { ... }
}

class Lion extends Mammalia {
    @Override
    public void breed() { ... }
    // public void breed() 는 자식 클래스에서 구체적으로 구현
}

인터페이스를 구현받고 추상 메서드를 구현부를 작성할 때 접근제어자 설정에 주의해야한다.
메서드를 오버라이딩(overriding) 할때는 부모의 메서드보다 넓은 범위의 접근제어자를 지정해야한다는 규칙이 존재한다.
인터페이스에서는 public abstract가 기본 설정되어있다. 따라서 구현부에서는 제어자를 public으로 설정해주어야 한다.

 

 

인터페이스 상속과 구현

https://inpa.tistory.com/

인터페이스 자체를 확장 시키고 싶다면 extends 를 통해 인터페이스를 상속하면 된다. 클래스는 다중 상속이 안되지만, 인터페이스끼리는 다중 상속이 가능하다.

클래스 상속과 마찬가지로 자손 인터페이스는 조상 인터페이스의 정의된 멤버를 모두 상속받는다. 그러나 필드의 경우 기본적으로 static 이기 때문에 구현체를 따라가지 않게 된다.(독립 상수)

단, 인터페이스에 클래스를 상속하는 행위는 불가능하다. 인터페이스는 클래스와 달리 Object 클래스가 최고 조상이 아니기 때문이다.

interface Member{
    // 멤버를 관리하는 인터페이스 
    void join();
}

interface Board{
    // 게시판을 관리하는 인터페이스
    void write(String msg);
}

// 인터페이스들을 하나의 인터페이스로 통합 상속
interface WebPage extends Member, Board { 
	// 인터페이스끼리 다중 상속하면 그대로 추상 멤버들을 물려 받음
}

// 클래스에 통합된 인터페이스를 그대로 상속
class Test implements WebPage {
	public void join() {
        System.out.println("회원가입");
    }
    public void write(String msg) {
        System.out.println("글작성");
    }
}

 

또한 아래와 같이 상속과 구현을 동시에 할 수 있다!

class 클래스이름 extend 상위클래스이름 implements 인터페이스이름 { ... }


인터페이스는 인터페이스로부터만 상속을 받을 수 있으며, 여러 인터페이스를 상속받을 수 있다.

 

 


 

 

자바8 인터페이스 구현 메소드

기존의 인터페이스의 메서드는 구현부를 직접 가질 수 없었다. 하지만 java8 부터는 디폴트(default) 메서드, 스태틱(static) 메서드를 통해 추상 클래스처럼 구현 메소드를 정의할 수 있게 되었다.

그래서 사실상 추상 클래스와의 차이점이 거의 사라졌다고 볼 수 있다.

 

 

 

default 메소드

조상 클래스에 새로운 메서드를 추가하는 것은 별 일이 아니지만, 인터페이스의 경우에는 보통 큰 일이 아니다.

인터페이스에 메서드를 추가한다는 것은, 추상 메서드를 추가한다는 것이고, 이 인터페이스를 구현한 기존의 모든 클래스들이 새로 추가된 메서드를 꼭 구현 해야하기 때문이다.

 

인터페이스가 변경되지 않는 코드가 제일 좋은 코드지만, 사실상 언젠간 변경이 이뤄지기 마련이다.

그래서 JDK의 설계자들은 디폴트 메서드(default method)라는 것을 고안해 내었다. 

 

디폴트 메서드는 추상 메서드의 기본적인 구현을 제공하는 메서드로, 추상 메서드가 아니기 때문에 디폴트 메서드가 새로 추가되어도 해당 인터페이스를 구현한 클래스에서는 변경하지 않아도 된다!

 

interface MyInterface{
	void method();
    default void newMethoid();
}

위와 같이 default 키워드를 추가하면 기존에 MyInterface를 구현한 클래스를 변경하지 않아도 된다. 

즉, 조상 클래스에 새로운 메서드를 추가한 것과 동일해 지는 것이다!

 

 

default 메소드 다중 상속 이슈

1. 여러 인터페이스의 디폴트 메서드 간의 충돌

인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩해서 해결해야 한다.

interface A{
    public void showA();
 
    default public void showAll(){
        System.out.println("A 디폴트 메서드");
    }
}

interface B{
    public void showB();

    default public void showAll(){
        System.out.println("B 디폴트 메서드");
    }
}

class Test implements A, B {
    @Override
    public void showA() {}
    @Override
    public void showB() {}

    // 두 인터페이스 디폴트 메서드중 A 인터페이스의 디폴트 메서드를 오버라이딩 하여 구현
    default public void showAll(){
        System.out.println("A 디폴트 메서드");
    }
}

public class Main {
    public static void main(String[] args) {
        Test t = new Test();
        t.showAll(); // A 디폴트 메서드 출력
    }
}

 

2. 디폴트 메서드와 조상 클래스의 메서드 간의 충돌

조상 클래스의 메서드가 상속되고, 디폴트 메서드는 무시된다.

interface A{
    public void showA();
 
    default public void showAll(){
        System.out.println("A 디폴트 메서드");
    }
}

abstract C{  
    default public void showAll(){
        System.out.println("C 인스턴스 메서드");
    }
}

class Test extends C implements A {
    @Override
    public void showA() {}  
}

public class Main {
    public static void main(String[] args) {
        Test t = new Test();
        t.showAll(); // C 인스턴스 메서드 출력
    }
}

 

이러한 규칙들을 다 외우기 귀찮다? -> 필요한 쪽의 메서드와 같은 내용으로 오버라이딩 하면 된다!

 

 

static 메서드

인터페이스의 static 메소드는 일반 클래스의 static의 메소드와 다를 바 없다. 

동일하게 선언되고, 동일하게 호출해서 사용이 가능하다.

interface Animal {  // 인터페이스 선언
	public abstract void cry(); 
    
    public static void showAnimal(){
    	System.out.println("static 메서드 호출!");
    }
} 

class Cat implements Animal { // 인터페이스 구현
    public void cry() {
        System.out.println("냐옹냐옹!");
    }
}

class Dog implements Animal { // 인터페이스 구현
    public void cry() {
        System.out.println("멍멍!");
    }
} 

public class Polymorphism03 {
    public static void main(String[] args) { 
    	Animal.showAnimal(); // static 메서드 호출! 출력
    }
}

 

 

인터페이스의 장점

지금까지 글이 잘 이해가 됐다면 장점이 얼추 머리 속에 그려질 것이다.

아래에 내용을 정리해보자면,

 



1. 개발 시간을 단축시킬 수 있다.
-> 메서드를 호출하는 쪽에서는 메서드의 내용에 관계없이 선언부만 알면 되기에 인터페이스의 규격만 미리 파악한다면 프로그램 작성이 가능하다.

2. 표준화가 가능하다.
-> 프로젝트에 사용되는 기본 틀을 인터페이스로 작성한 다음, 개발자들에게 인터페이스를 구현하여 프로그램을 작성하도록 함으로써 보다 일관되고 정형화된 프로그램의 개발이 가능하다.

3. 서로 관계없는 클래스들에게 관계를 맺어줄 수 있다.
-> 서로 상속관계에 있지도 않고, 같은 조상클래스를 가지고 있지 않은 클래스들에게 하나의 인터페이스를 공통적으로 구현하도록 함으로써 관계를 맺어줄 수 있다.

4. 독립적인 프로그래밍이 가능하다.
-> 인터페이스를 이용하면 클래스의 선언과 구현을 분리시킬 수 있기 떄문에 실제구현에 독립적인 프로그램을 작성하는 것이 가능하다. 클래스와 클래스간의 직접적인 관계를 인터페이스를 이용해서 간접적인 관계로 변경하면, 한 클래스의 변경이 관련된 다른 클래스에 영향을 미치지 않는 독립적인 프로그래밍이 가능하다.

 

 

 

#참고자료

자바의 정석(남궁성 지음)

 

Inpa Dev 👨‍💻

성장 욕구가 가파른 초보 개발자로서 공부한 내용을 쉽게 풀어쓴 기술 개발자 블로그를 운영하고 있습니다.

inpa.tistory.com

 

코딩교육 티씨피스쿨

4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등

tcpschool.com

 

profile

Yoon DevLog

@usnooy._.

백엔드 개발자 성장일기 -