1. 메소드 내용이 없는 interface

자바에서 .class 파일을 만들 수 있는 것에는 클래스만 있는 것이 아닙니다. interface(인터페이스)와 abstract 클래스라는 것이 있습니다. 시스템을 만들 때 설계 단계에서 프로그램을 어떻게 만들 것 인지 정리합니다. 이 때 어떤 메소드를 만들지, 어떤 변수를 만들지를 정리하는 작업도 같이 합니다. 그런데, 내용들을 문서에만 정리하면 나중에 메소드 관련 내용들이 변경되면 문서도 수정해야 하므로, 2중 3중의 일이 됩니다. 그래서, 이 설계 단계에서 인터페이스라는것을 만들어 두면 개발할 때 메소드의 이름을 어떻게 할지, 매개 변수를 어떻게 할지를 일일이 고민하지 않아도 됩니다. 게다가 개발자의 역량에 따라 메소드 이름과 매개 변수 이름이 천차만별일 수가 있는데, 그러한 격차를 줄이는 데도 큰 역할을 합니다

 

 

● 설계시 선언해 두면 개발할 때 기능을 구현하는 데에만 집중할 수 있다.

 

● 개발자의 역량에 따른 메소드의 이름과 매개 변수 선언의 격차를 줄일 수 있다.

 

● 공통적인 인터페이스와 abstract 클래스를 선언해 놓으면, 선언과 구현을 구분할 수 있다.

 

2. 인터페이스 구현

MemberDTO를 관리하는, MemberManager라는 클래스를 만들어야 한다고 가정합시다. 실제 코드는 만들지 않더라도 어떤 메소드들이 있어야 하는지를 정의하려고 할 때 인터페이스를 사용하면 됩니다. 인터페이스는 다음과 같이 정의합니다. 

package c.impl;



import c.inheritance.MemberDTO;



public interface MemberManagerInterface {

        public boolean addMember(MemberDTO member);

        public boolean removeMember(String name, String phone);

        public boolean updateMember(MemberDTO member);

}

여기서 가장 중요한 것은 인터페이스 선언부는 public class로 시작하지 않고 public interface로 시작한다는 것입니다. 이렇게 interface 내부에 선언된 메소드들은 몸통이 있으면 안됩니다. 

 

인터페이스의 파일 이름은 지금까지 만든 것처럼 확장자를 .java 파일로 만들면 됩니다. 파일을 컴파일 하는 방법은 일반 클래스와 동일합니다. 하나의 팁으로는 인터페이스의 이름을 지을 때, ImemberManager라고 할 수도 있습니다. 꼭 Interface라고 이름의 뒷 부분에 붙일 필요는 전혀 없습니다. 

 

다음과 같이 MemberManager라는 클래스를 만들어 봅시다.

package c.impl;

import c.inheritance.MemberDTO;

public class MemberManager implements MemberManagerInterface{

}

클래스 선언문을 보면 Implements라는 단어 뒤에 MemberManagerInterface라고 방금 만든 인터페이스를 추가하였습니다. 이와 같이 만들어져있는 인터페이스를 적용한다고 할 때에는 클래스 선언문에서 클래스 이름 뒤에 implements라는 예약어를 쓴 후 인터페이스들을 나열하면 됩니다. 끝에 s가 붙는 이유는 클래스 자체가 3인칭 단수이므로 붙는 것 입니다. 

 

그런데, 자바는 상속이 하나밖에 안 된다고 했습니다. 자바에서 인터페이스를 implements하면 "상속한다"라고 하지 않고, "구현 한다"고 합니다. 즉, implements한다고 해서 상속 받는다는 것이 아니라, 해당 클래스에서 구현해야 하는 인터페이스들을 정의함으로써 클래스에 짐을 지어 주는 것 입니다. 

 

여러분들이 인터페이스를 구현할 경우에는 반드시 인터페이스에 정의된 메소드들의 몸통을 만들어 주어야 합니다. MemberManager 클래스는 적어도 다음과 같이 구현 해야만 합니다.

package c.impl;

import c.inheritance.MemberDTO;

public class MemberManager implements MemberManagerInterface{

    public boolean addMember(MemberDTO member) {

        return false;

    }

    public boolean removeMember(String name, String phone) {

        return false;

    }

    public boolean updateMember(MemberDTO member) {

        return false;

    }

}

이렇게 MemberManagerInterface에 정의된 메소드들을 모두 구현해야만 컴파일이 정상적으로 수행됩니다. 이제 개발자들이 해야 하는 일은 대충 return false만 적어 놓은 메소드들이 실제로 Member 정보를 저장하고, 삭제하고, 수정하는 일을 할 수 있도록 제대로 된 코드를 만드는 것입니다. 바로 이 작업이 개발 프로세스 중에서 "개발"에 해당합니다. 또한 인터페이스를 설계를 위해서만 만드는 것이 아닙니다. 인터페이스의 또 다른 용도는 외부에 노출되는 것을 정의해 놓고자 할 때 사용합니다. 

 

인터페이스 컴파일시 만들어지는 파일이 .class의 확장자를 가진다고 해서 인터페이스를 그대로 사용할 수는 없습니다. 생성자도 없고, 메소드의 내용 중 아무것도 채워져 있지 않기 때문입니다. 예를 통해서 살펴봅시다. 다음과 같이 ImplementsSample 클래스에 main() 메소드를 만듭시다.

package c.impl;

public class ImplementsSample {

    public static void main(String[] args) {

        MemberManagerInterface manager = new MemberManagerInterface();

    }

}

이제 컴파일을 해봅시다.

 

에러메시지 1

컴파일러가 "아무것도 구현하지 않았는데 왜 얘로 초기화하는 것이냐"라며 에러를 내뿜습니다. 앞에서 배운 상속 관계에 있는 클래스의 형 변환에 대해서 생각하면 알 수 있습니다.

package c.impl;

public class ImplementsSample {

    public static void main(String[] args) {

        MemberManagerInterface manager = new MemberManager();

    }

}

겉으로 보기에 manager의 타입은 MemberManagerInterface입니다. 그리고, MemberManager 클래스에는 인터페이스에 선언되어 있는 모든 메소드들이 구현되어 있습니다. 따라서, 실제 manager의 타입은 MemberManager가 되기 때문입니다. 

 

인터페이스의 변수는 public static final으로 자동 선언됩니다. 컴파일을 하면 자동으로 변수 선언 앞에 public static final이 앞에 붙습니다. 마찬가지로 메소드의 경우도 public abstract으로 자동 선언됩니다. 인터페이스는 클래스와 달리 기본 접근 제어자는 public 입니다. 메서드를 private로 설정하면 실제 구현체를 인터페이스 내에 구현해야합니다.

 

인터페이스는 또한 다중 상속이 가능합니다.

public interface Walkable {
  void walk();
}

public interface Flyable {
  void fly();
}

public interface Moveable extends Walkable, Flyable {
}

이를 구현한다면 다음과 같습니다.

public class Bat implements Moveable {
  @Override
  public void walk() {
    // ...
  }

  @Override
  public void fly() {
    // ...
  }
}

인터페이스는 클래스와 달리 기본 접근 제어자는 public 입니다. 메서드를 private로 설정하면 실제 구현체를 인터페이스 내에 구현해야합니다.

4. default 접근제어자

자바8버전 이상부터는 인터페이스에서 default 접근 제어자를 사용할 수 있게 되었습니다. 이는 인터페이스 내에서 직접 메서드를 구현한다는 의미의 접근 제어자입니다. 이를 구현한 클래스는 오버라이딩 없이 이 default 메서드를 사용할 수 있고, 오버라이딩 또한 가능합니다. 

 

이로 인해 다중 상속일 때 고려해야할 사항이 많아지는 등 복잡했습니다. 하지만 이를 만든 주된 이유는 라이브러리 업데이트 때문입니다. 인터페이스는 라이브러리나 프레임워크에서 매우 자주 사용된다. 라이브러리를 업데이트할 때 인터페이스에 추가된 기능이 있다면 이를 구현한 클래스 역시 추가해주어야 합니다. 하지만 이 라이브러리를 사용하는 사용자가 해당 인터페이스를 구현한 클래스가 있다면 이를 구현하지 않아 컴파일 에러가 발생할 것입니다. 이러한 심각한 불편함을 해소하기 위해 default 라는 키워드를 만들었다고 한다.

5. 인터페이스의 사용 이유

  • 인터페이스를 이용하여 클래스를 구현하면 다른 클래스와 대체가 유연해서 유지보수가 편해진다는 장점이 있습니다.
  • 개발자끼리 협업을 할때 정해진 이름을 사용해서 구현할 수 있습니다.
  • 상속받은 클래스 또는 인터페이스의 메소드를 재정의하여 서로 다른 행동을 만들 수 있습니다. (다형성 활용)
  • 설계시 선언해 두면 개발할 때 기능을 구현하는 데에만 집중할 수 있다.
  • 프레임워크, 공용 컴포넌트, 유틸 등을 개발할 때 효과적입니다. 대신 일반 웹 개발 프로젝트에서는 복잡도만 증대시키는 상황이 많기 때문에 잘 사용해야합니다.

6. 인터페이스와 상속의 차이

다형성을 활용하기 위해서 기본적으로 자바에서는 클래스 상속과 인터페이스 구현으로 나뉩니다. 이를 선택하는 것은 개발자의 몫입니다. 이를 도와주는 판단 기준이 있습니다.

  • 상속보다는 위임을 사용해라
  • 다형성을 위한 것이면 클래스 상속보다는 인터페이스를 구현하라

위를 보면 상속을 대부분 추천하지 않습니다. 그만큼 비용이 큰 작업이기 때문입니다. 변하지 않는 구조에서 상속을 사용하면 중복 코드를 제거하고 깔끔하게 코드를 작성할 수 있지만, 변화가 발생하는 순간 상속 구조는 깨지기 쉽습니다.

 

예를 들어 상속 관계에서 부모 클래스에 기능이 추가되었는데, 자식 클래스 중 하나에서 이를 사용할 수 없도록 해야 한다면 상속 구조가 깨질 수 있습니다. 따라서 상속은 기본적으로 Is-A 관계가 확실하고, 설계 이후에 변화가 없는 곳에서 사용해야 합니다. 하지만 현실은 설계 이후에도 기능 변화가 비일비재 합니다. 그래서 대부분 인터페이스를 사용합니다.

7. 일부 완성되어 있는 abstract 클래스

인터페이스도 아니고 그렇다고 클래스라고도 하기 힘든 abstract 클래스에 대해서 알아보겠습니다. 마찬가지로, abstract 클래스는 자바에서 마음대로 초기화하고 실행할 수 없습니다. 그래서 그 abstract 클래스를 구현해 놓은 클래스로 초기화 및 실행이 가능합니다. 

package c.impl;

import c.inheritance.MemberDTO;

public abstract class MemberManagerAbstract {

    public abstract boolean addMember(MemberDTO member);

    public abstract boolean removeMember(String name, String phone);

    public abstract boolean updateMember(MemberDTO member);

    public void printLg(String data) {

        System.out.println("Data="+data);

    }

}

abstract 클래스는 선언 시 class 라는 예약어 앞에 abstract라는 예약어를 사용한 것을 볼 수 있습니다. 그리고, 몸통이 없는 메소드 선언문에는 abstract라는 예약어를 사용한 것을 볼 수 있습니다. 

 

abstract 클래스에는 구현된 메소드가 있을 수 있기 떄문에 확장해서 사용한다고 이야기해 주어야만 합니다. abstract 클래스를 구현하는 예는 다음과 같습니다.

package c.impl;

import c.inheritance.MemberDTO;

public class MemberManager2 extends MemberManagerAbstract{

    public boolean addMember(MemberDTO member) {

        return false;

    }

    public boolean removeMember(String name, String phone) {

        return false;

    }

    public boolean updateMember(MemberDTO member) {

        return false;

    }

}

이렇게 abstract 클래스를 만든 이유는 무엇일까요? 인터페이스를 선언하다 보니, 어떤 메소드는 미리 만들어 놓아도 전혀 문제가 없는 경우가 발생합니다. 그렇다고, 해당 클래스를 만들기는 좀 애매하고 말입니다. 이럴 때 사용하는 것이 바로 abstract 클래스입니다. 정리해보겠습니다.

 

● abstract 클래스는 클래스 선언 시 abstract라는 예약어가 클래스 앞에 추가되면 된다.

 

● abstract 클래스 안에는 abstract로 선언된 메소드가 0개 이상 있으면 된다.

 

● abstract로 선언된 메소드가 하나라도 있으면, 그 클래스 반드시 abstract으로 선언되어야만 한다.

 

● abstract 클래스는 몸통이 있는 메소드가 0개 이상 있어도 전혀 상관 없으며, static이나 final 메소드가 있어도 된다.

 

 

인터페이스

abstract 클래스

클래스

선언 시 사용하는 예약어 interface abstract class class
구현 안 된 메소드 포함 가능 여부 가능(필수) 가능 불가
구현된 메소드 포함 가능 여부 불가 가능 가능(필수)
static 메소드 선언 가능 여부 불가 가능 가능
final 메소드 선언 가능 여부 불가 가능 가능
상속(extends) 가능 불가 가능 가능
구현(implements) 가능 불가 불가

 

'Java' 카테고리의 다른 글

[Java] 18. enum 클래스  (0) 2019.04.09
[Java] 17. 상속과 final  (0) 2019.04.08
[Java] 15. Object 클래스  (0) 2019.03.30
[Java] 14. 다형성(Polymorphism)  (0) 2019.03.26
[Java] 13. 참조 자료형의 형 변환  (0) 2019.03.23
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기