ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [8주차]인터페이스
    스터디/[whiteship]JAVA 2021. 1. 6. 21:16

    목표

    자바의 인터페이스에 대해 학습하세요.


    학습할 것 (필수)

    • 인터페이스 정의하는 방법
    • 인터페이스 구현하는 방법
    • 인터페이스 레퍼런스를 통해 구현체를 사용하는 방법
    • 인터페이스 상속
    • 인터페이스의 기본 메소드 (Default Method), 자바 8
    • 인터페이스의 static 메소드, 자바 8
    • 인터페이스의 private 메소드, 자바 9

    인터페이스 정의

    자바에서 인터페이스는 추상메서드와 상수의 모음을 인터페이스라 말한다. 여기서 추상 메서드란 다음과 같다.

    https://velog.io/@codemcd/인터페이스Interface

    인터페이스 Walkable내 walk란 메서드를 보면 알 수 있듯이 구현이 되어있지 않고 선언만 되어 있다. 우리는 이것을 '추상 메소드'라 칭한다. 이러한 추상 메서드와 상수를 가진 인터페이스는 반드시 상속받는 클래스에서 구현해 주어야 한다. 

    왜? -> 기본적으로 인터페이스는 자바의 다형성이란 개념을 위해서 사용된다. 즉 상속받는 클래스에서 인터페이스를 구현해 똑같은 이름의 메서드라도 다른 기능을 동작하게 해 준다는 것. 즉 직접적으로 구현해주어야 사용이 가능하다.

     

    추상클래스와 인터페이스의 차이점

    추상 클래스는 구현부 내부에 일반 메소드 뿐만 아니라 추상 메서드가 하나 이상 포함 한다는 개념을 가지고 있다.(상속받는 클래스에서 구현해야 하기 때문에 final키워드를 같이 사용 못함). 추상 클래스와 인터페이스의 공통점은 new 연산자로 객체를 생성할 수 없는 것과 선언부만 존재한다는 것이다.

     

    차이점은 다음과 같다.

    차이점
    추상 클래스(abstract class) 인터페이스(interface)
    일반 메소드 포함가능 모든 메서드는 추상 메서드이다
    다중상속 불가능 다중상속 가능
    상수, 변수 필드가 포함 상수필드만 포함 가능

    그렇다면 둘 중 무엇을 사용해서 기능을 구현해야 할 것인가?

    만약 모든 클래스가 인터페이스를 사용해야 한다면 추상 메서드를 모두 구현해주어야 한다는 번거로움이 생긴다. 따라서 공통된 기능들이 있을경우엔 추상 클래스를 하지만 자바에선 다중 상속이 불가능 하기 때문에 각각 다른 기능들을 구현할 때에는 인터페이스를 구현하는 것이 옳다. 기본적으로 추상 클래스와 인터페이스는 다음과 같은 뜻을 가진다.

     

    추상 클래스는 IS-A -> ~이다. -> 조상 클래스와 같은 기능을 할 수 있는 메서드가 있다.

    인터페이스는 HAS-A -> ~을 할 수 있는 -> 조상 클래스와 같은 이름의 기능을 하는 메서드가 있다.

     

    글만 보면 제대로 이해 되지 않을것이다. 추상 클래스 "~이다."는 공통 역활을 하는 기능들이 있다는 뜻이고, 인터페이스 "~을 할 수 있는"이란 뜻은 인터페이스내 정의된 추상 메서드를 통해서 ~을 할 수 있다란 의미를 가진다. 

    더 이해를 원한다면 여기를 참조해보자 -> myjamong.tistory.com/150


     

    인터페이스 구현하는 방법

    인터페이스 정의

    인터페이스 구현 방법은 다음과 같다.

    접근 지정자 interface키워드 인터페이스 이름{

    ... 상수나, 추상메서드

    }


    인터페이스의 필드는 정적 멤버로 객체 생성 이전에 Method영역에 올라가 메모리상에 존재해야 하기 때문에 선언과 동시에 초기화를 진행해주어야 한다.


    이러한 인터페이스를 컴파일해 바이트 코드를 보면

    컴파일 된 바이트 코드

    위의 그림과 같이 정수형 변수 a는 static final 키워드를 명시를 안 해 주어도 자동으로 컴파일할 때 키워드가 붙여진다.

    이러한 클래스나 인터페이스 정보(접근 지정자, 추상 메서드 정보, static 변수)와 static 키워드로 작성된 변수들은 우리가 1주 차 때 공부했던 JVM영역 runtime data area의 스레드 공유 영역인 Method area에 영역이 할당된다.

    인터페이스의 추상메서드를 구현한 example 클레스

    ※인터페이스 내 추상 메서드는 반드시 구현되어야 해당 인터페이스의 객체가 생성이 가능하다.

    ※인터페이스는 추상 메서드를 구현한다는 의미 해서 Extends가 아닌 implements 키워드를 통해 상속받고 구현한다.


    인터페이스 레퍼런스를 통해 구현체를 사용하는 방법

     

    인터페이스를 implements로 상속받아 구현한 과정을 위에서 보았다. 이렇게 상속받아 구현한 클래스는 객체 생성이 가능해진다.

    test.work()호출 결과

    인터페이스는 클래스 레벨과 동일하며 객체의 주소 번지를 가리키는 레퍼런스 타입으로 객체를 생성하고 접근이 가능하다.

    또한 exampleInterface의 경우 example클래스에서 해당 인터페이스를 구현했기 때문에 exampleInterface의 레퍼런스를 만든 후 해당 인터페이스를 구현한 example클래스 객체를 생성하고 참조할 수 있다.

    결과는 동일하다.

    Q. 인터페이스를 구현한 객체를 생성할 때, 구현한 클래스로 레퍼런스 타입을 주는 게 옳은 것인가? 아니면 해당 인터페이스를 레퍼런스 타입으로 주는 것이 나을까? -> EX)exampleInterface test로 하는 것이 좋을지 example test로 래퍼런스 타입을 주는 것이 좋을지.


    인터페이스 상속

    인터페이스와 클래스는 같은 레퍼런스 타입의 레벨이다 하지만 상속에선 분명한 차이가 있다.

     

    1. 클래스의 상속은 다중 상속은 불허한다.

    2. 인터페이스의 상속은 다중 상속이 가능하다.

    3. 클래스를 상속받고, 인터페이스도 상속받으려면 다음과 같이 사용한다.


    인터페이스의 기본 메서드 (Default Method), 자바 8

    https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html

    번역해보자면, 기본 메소드 인터페이스의 다른 코드로 쓰인 이진 호환성과 라이브러리에 새로운 기능을 추가할 수 있게 한다.

    어렵지만 쉬운 말로 기존에 구현한 인터페이스에 새로운 추상 메서드를 추가하면, 그 인터페이스를 구현하는 클래스가 추가된 추상 메서드를 구현해야 하는 문제가 생기는데, 해당 인터페이스를 implements 하는 클래스에서 추상 메서드를 모두 구현하는 것이 아닌 특정한 클래스에만 새로운 추상 메서드를 Override 해서 쓰게 해주는 기능을 제공해준다.

     

    기존 인터페이스에 eat이라는 추상 메서드를 추가해보자.

    이걸 implements 하는 클래스인 example 클래스를 보면 오류가 난다 -> 추상 메서드를 다 구현 안 했으니까.

    exampleInterface를 implements 하는 클래스들이 만약 1개가 아닌 10개 100개였다면? -> 다 수정해야 하는데 이런 것을 방지해 특정한(추상 메서드를 사용해야 하는) 클래스에서만 새로 추가된 추상 메서드를 구현해 쓸 수 있게 해주는 것이 Default Method이다.

     

    default 메서드 추가

     

    example 클래스에서 default method인 eat 메서드를 구현 안 해주어도 오류가 발생하지 않는다.

    default method는 abstract키워드 대신 default 키워드를 사용하고, body를 가지고 실행 내용까지 작성이 가능하다.

    물론 override 해서 재정의도 가능하다.

    override 실행 결과


    인터페이스의 static 메소드, 자바 8

    default 메서드와 달리 static 메서드는 재정의가 불가하다.

    객체를 생성하기 전에 메모리에 로드되기 때문에, 인터페이스 명으로 메서드를 호출해야 한다.

    default 메서드와 동일 한 점은 interface안에서 구현이 가능하단 점이다.

    기본적으로 static 메서드는 public이다.


    인터페이스의 private 메소드, 자바 9

    자바 8에선 default method와 static 메서드를 사용하면 기본적으로 public 하다. 외부에서 사용되므로 캡슐화 부분에 있어서도 지켜지지 않는 문제가 있다 따라서 자바 9에서 내부적으로만 사용하는 추상 메서드는 private 키워드를 사용이 가능하게 해 주었다.

    소스 코드를 보며 이해하자.default 메서드인 헬스케어와 상담 서비스가 있고, 각 메서드에서 서비스를 등록한 고객 이름과 등록한 서비스를 출력하는 default메서드를 작성했다 가정하면 다음과 같이 작성할 수 있다. 

    public interface ICustomerService {
     
      default void healthcare(String name) {
        System.out.println(name + " registers for customer service.");
        System.out.println("-- get HealthCare Service.");
      }
     
      default void consult(String name) {
        System.out.println(name + " registers for customer service.");
        System.out.println("-- get Consultation Service.");
      }
    }

    좀 더 깔끔하고 명확하게 변경하면 다음과 같다.

    public interface ICustomerService {
     
      default void healthcare(String name) {
        register(name);
        System.out.println("-- get HealthCare Service.");
      }
     
      default void consult(String name) {
        register(name);
        System.out.println("-- get Consultation Service.");
      }
     
      default void register(String name) {
        System.out.println(name + " registers for customer service.");
      }
    }

    하지만 이렇게 하면 자동적으로 register메서드는 public 하게 되어 인터페이스 외부에서도 참조가 가능해진다.

    package oneweeks;
    
    public class test {
        public static void main(String[] args) {
            Customer customer = new Customer();
            customer.register("Kim");
        }
    }

    외부에서 객체 생성후 참조.

    하지만 register default 메서드가 외부에서 참조되지 않고, 내부적으로만 참조되고 작동하는 기능으로 구현하고 싶다면 다음과 같이 private 메서드로 작성해주면 된다.

    public interface ICustomerService {
     
      default void healthcare(String name) {
        register(name);
        System.out.println("-- get HealthCare Service.");
      }
     
      default void consult(String name) {
        register(name);
        System.out.println("-- get Consultation Service.");
      }
     
      private void register(String name) {
        System.out.println(name + " registers for customer service.");
      }
    }

    이렇게 하면 객체를 생성하고도 외부에서 참조하지 못한다.

    [REFERENCES]

    grokonez.com/java/java-9-private-interface-method

Designed by Tistory.