Skip to content

Latest commit

 

History

History
565 lines (415 loc) · 31.1 KB

File metadata and controls

565 lines (415 loc) · 31.1 KB

interface와 abstract class차이에 대해서 설명해주세요.

추상 클래스(abstract class)

추상 클래스(abstract class)

  • abstract 키워드로 선언된 클래스
  • 하나 이상의 추상 메서드를 포함하는 클래스
  • 반드시 사용되어야 하는 메서드를 추상 클래스에 추상 메서드로 선언해 놓으면, 해당 클래스를 상속받는 모든 클래스에서는 이 추상 메서드를 반드시 재정의해야 한다.

추상 메서드(abstract method)

  • 정의: 자식 클래스에서 반드시 오버라이딩해야만 사용할 수 있는 메서드

    오버라이딩(Overriding): 상속 관계에 있는 부모 클래스에서 이미 정의된 메소드를 자식 클래스에서 같은 시그니쳐를 갖는 메서드로 다시 정의하는 것

문법

abstract class 클래스이름 {
  ...
  abstract 반환타입 메서드이름();
  ...
}

예시 코드

// abstract class
public abstract class Person {
  
  private String name;
  private String gender;
  
  public Person(String nm, String gen){
    this.name=nm;
    this.gender=gen;
  }

  @Override
  public String toString(){
    return "Name="+this.name+"::Gender="+this.gender;
  }
  
  // abstract method
  public abstract void work();

  public void changeName(String newName) {
    this.name = newName;
  }	
}
  • 위 코드에서 알 수 있다시피 추상 클래스는 추상 메서드를 포함하고 있다는 점을 제외하면, 일반 클래스와 모든 점이 같다.
  • 즉, 생성자와 필드, 일반 메서드도 포함할 수 있다.

추상화(Abstraction)란?

  • 클래스들의 중요하거나 공통된 성질들을 추출하여 부모(슈퍼) 클래스를 선정하는 개념과, 이벤트 발생의 정확한 절차나 방법을 정의하지 않고 대표할 수 있는 표현으로 대체하는 것을 말한다.
  • 추상화를 통해 OCP(Open Closed Principle)을 지킬 수 있다.

객체 지향 프로그래밍의 추상화

제어 추상화

  • 어떤 클래스의 메서드를 사용하는 사용자에게 해당 메서드의 작동방식과 같은 내부로직을 숨기는 것을 말한다.
  • for, while 문도 사실 반복하는 개념을 제어 추상화 한 것이다.
    • 내부 CPU 동작이 어떻게 이루어지는가는 알 필요없이 반복 기능이 추상화된 것

데이터 추상화

  • 대상을 간단한 개념으로 일반화 하는 과정
  • 추상화를 하면 할 수록 객체의 디테일함이 사라지고 공통된 특징만 남게 된다.
    abstract class 전자제품 {
        전원기능();
    }
    
    abstract class 통신기기 extends 전자제품 {
        통화기능();
    }
    
    abstract class 휴대폰 extends 통신기기 {
        카메라기능();
        게임기능();
    }
    
    class 아이폰 extends 휴대폰 {
        전원기능() { ... }
        통화기능() { ... }
        카메라기능() { ... }
        게임기능() { ... }
        애플 제품 연동기능() { ... }
    }
    // → 최종적으로 아이폰 class는 전원, 통화, 카메라, 게임, 애플 연동 5가지 기능을 정의하여 설계된다

추상 클래스의 용도는?

  • 클래스라는 것은 인스턴스를 생성해주는 템플릿 같은 개념이다.

    인스턴스란 클래스를 통해서 구현해야할 대상(객체)이 실제로 구현된 구체적인 실체를 말한다. 클래스를 사용하여 힙 영역(Heap Area)에 새로운 인스턴스(객체)를 생성할 수 있다.

  • 우리는 이러한 인스턴스 객체 자료형을 사용하여 보다 구조적으로 프로그램을 설계 할 수 있다.
  • 즉, 추상 클래스는 클래스에 추상화를 접목시켜 보다 구조적으로 객체를 설계하고, 프로그램의 유지 보수성을 올려준다.

추상 클래스의 활용

공통 멤버의 통합으로 중복 제거

  • 자주 사용될 것이 예상되는 기능을 모아놓은 추상 클래스를 이용
  • 편하게 재사용을 함으로써 유지보수 효율화를 추구할 수 있다.

구현의 강제성을 통한 기능 보장

  • 추상 메서드를 포함하는 것 외엔 일반 클래스와 다를 것 없는 추상 클래스를 사용하는 이유는 무엇일까?
  • 필수적인 메서드를 구현하지 않아 발생할 수 있는 오류를 컴파일 에러를 통해 미리 방지할 수 있다는 점에서 안정성과 구조적인 프로그래밍을 보장해주기 때문이다.

규격에 맞는 설계 구현

  • 추상 클래스를 상속받아서 미리 정의된 공통 기능들을 구현하고, 실체 클래스에서 필요한 기능들을 클래스 별로 확장시킴으로써 소스 수정시 다른 소스의 영향도를 적게 가져가면서 변화에는 유연하게 만들 수 있다.

인터페이스(Interface)

인터페이스(Interface)

  • 다른 클래스를 작성할 때 기본이 되는 틀을 제공하면서, 다른 클래스 사이의 중간 매개 역할까지 담당하는 일종의 추상 클래스를 의미
  • 인터페이스는 필드를 선언할 수 있지만, 변수가 아닌 상수(final) 로만 정의할 수 있다.
  • public static finalpublic abstract 제어자는 생략 가능하다.
    • 생략 가능 이유: 인터페이스에 정의된 모든 멤버에 적용되는 사항이기 때문(편의 기능 제공)

문법

접근제어자 interface 인터페이스이름 {

    public static final 타입 상수이름 = 값;

    ...

    public abstract 메소드이름(매개변수목록);

    ...

}

예시 코드

public interface TV {
    int MAX_VOLUME = 10; // public static final 생략 가능
    int MIN_VOLUME = 10;

    void turnOn(); // public abstract 생략 가능
    void turnOff();
    void changeVolume(int volume);
    void changeChannel(int channel);
}

인터페이스 구현

  • 인터페이스도 추상 클래스처럼 그 자체로는 인스턴스 생성 불가능
  • impliments 키워드를 쓴 후에 인터페이스를 작성
  • 인스턴스를 상속받은 자식 클래스는 인터페이스가 포함하고 있는 추상 메서드를 구체적으로 구현
  • 인터페이스의 가장 큰 특징은 여러 개를 다중 구현(다중 상속)이 가능하다는 것이다.
  • 자식 클래스에 클래스 상속(extends)와 인터페이스 구현(implements)는 동시에 가능하다.

💡 인터페이스에서 extends 키워드 대신 implements 라는 ‘구현’ 이라는 키워드를 사용하는 이유

  • 상속은 클래스간의 부모 - 자식 관계를 연관 시키는데 의미가 중점된다.
  • 구현은 클래스를 확장시켜 다양히 이용하는데 중점이 된다.

💡인터페이스를 구현받고 추상 메서드를 구체적으로 구현할 때 접근제어자 설정에 주의해야 한다.

  • 기본적으로 메서드를 오버라이딩(overriding) 할 때는 부모의 메서드보다 넓은 범위의 접근제어가를 지정해야한다.
  • 따라서 인터페이스의 추상 메서드는 기본적으로 public abstract 가 생략된 상태이기 때문에 반드시 자식 클래스의 메서드 구현부에서는 제어자를 public 으로 설정해 주어야 한다.

인터페이스 일부 구현(추상 클래스)

  • 인터페이스의 메서드 중 일부만 구현한다면 abstract 를 붙여서 추상 클래스로 선언해야 한다.

    interface Animal {
        void walk();
        void run();
        void breed();
    }
    
    // Animal 인터페이스를 일부만 구현하는 포유류 추상 클래스
    abstract class Mammalia implements Animal {
        public void walk() { ... }
        public void run() { ... }
        // public void breed() 는 자식 클래스에서 구체적으로 구현하도록 일부로 구현하지 않음 (추상 메서드로 처리)
    }
    
    class Lion extends Mammalia {
        @Override
        public void breed() { ... }
    }

인터페이스 자체 상속

  • 클래스의 상속과 마찬가지로 자손 인터페이스는 조상 인터페이스에 정의된 멤버를 모두 상속받는다.
  • 클래스와 달리 인터페이스끼리는 다중 상속이 가능하다.
    • 메서드 구현부가 없기 때문에 충돌 가능성이 없음
  • 참고로 인터페이스에 클래스를 상속하는 행위는 불가능한데, 왜냐하면 인터페이스는 클래스와는 달리 Object 클래스가 최고 조상이 아니기 때문이다.

인터페이스 상수 필드 상속 관계

  • 필드의 경우 기본적으로 static 이기 때문에 구현체를 따라가지 않게 된다. (독립 상수)

  • 클래스의 상속일 경우 클래스 필드 넘버끼리 상속되어 덮어 씌워지지만, 인터페이스의 필드들은 모두 public static final 이기에, 서로 상속을 해도 독립적으로 운용

    interface Iflower {
        int ex = 10; // 각각 public static final
    }
    
    interface IPlant extends Iflower {
        int ex = 20; // 각각 public static final
    }
    
    class Tulip implements IPlant {
        int ex = 30; // 그냥 인스턴스 변수
    }
    
    public class Main {
    	public static void main(String[] args) {
            // 클래스 타입 객체로 ex 멤버에 접근하면, 클래스 인스턴스 변수로 접근
            Tulip t =  new Tulip();
            System.out.println(t.ex); // 30
    
            // 인터페이스 타입 객체로 멤버에 접근하면, 인터페이스 static 상수로 접근
            Iflower a = new Tulip();
            System.out.println(a.ex); // 10 - 좋지않은 방법
            System.out.println(Iflower.ex); // 10 - 클래스 static 처럼 '인터페이스.멤버' 로 접근
    
            IPlant b = new Tulip();
            System.out.println(b.ex); // 20 - 좋지않은 방법
            System.out.println(IPlant.ex); // 20 - 클래스 static 처럼 '인터페이스.멤버' 로 접근
        }
    }

Java 8 인터페이스

  • 인터페이스는 java 8부터 디폴트 메서드스태틱 메서드를 통해 추상 클래스처럼 구현 메서드를 정의할 수 있게 되었다.
  • 이전 인터페이스를 사용하여 java8의 람다 표현식 기능을 활용할 수 있도록 이전 버전과의 호환성을 위해 추가되었다.
    • Java8 버전에 새롭게 추가된 스트림이나 람다와 같은 함수형 프로그래밍을 컬렉션(Collection) 클래스에서 사용하기 위해, 기존에 만들어둔 인터페이스들을 구현하고 있는 컬렉션 클래스들의 구조에서 특정한 기능을 추가해야 되는 상황이 오게 된다.

    • 기존의 인터페이스에 추상메서드를 추가하면? 해당 인터페이스를 구현하고 있는 모든 구현 클래스도 변경이 필요해진다.

      → 이 문제를 디폴트 메서드를 추가해서 해결했다.

함수형 인터페이스(Functional Interface)

  • Java 8에 도입된 함수형 인터페이스는 인터페이스가 함수처럼 동작하기 때문에 함수형 인터페이스라고 한다.
  • 함수형 인터페이스는 하나의 추상 메서드를 가지고 있어서 SAM 인터페이스 (Single Abstract Method Interface) 라고도 한다.

default 메서드

  • 디폴트 메서드는 앞에 키워드 default 를 붙이며 일반 메서드처럼 구현부 { ... } 가 있어야 한다.

  • 디폴트 메서드 역시 접근제어자가 public 이며 생략 가능하다.

  • 자식 클래스(구현체)에서 default 메서드를 오버라이딩 하여 재정의 가능하다.

  • 보통 인터페이스를 구현한 이후, 수정 과정에서 인터페이스 모든 구현체에게 수정 없이 광역으로 함수를 만들어주고 싶을 때 사용된다. (대신 모든 구현체가 원하는 값을 return 하게 보장하기 위해 @implSpec 자바 doc 태그를 사용해 문서화 해줘야 한다)

    자바 doc 태그: ****JAVA 소스코드에서 API 문서를 html 태그형식으로 작성하게 해주는 도구

  • 주의 해야할 점: 인터페이스는 Object 클래스를 상속받지 않기 때문에, Object 클래스가 제공하는 기능(equals, hasCode)는 기본 메소드로 제공할 수 없다. 따라서 구현체가 직접 재정의를 해주어야 한다.

default 메소드 다중 상속 문제

  1. 다중 인터페이스들 간의 디폴트 메서드 충돌
  • 애초에 똑같은 디폴트 메서드를 가진 두 인터페이스를 하나의 클래스에 구현하고 아무런 조치를 취하지 않으면 컴파일 자체가 되지 않는다.

  • 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩 하여 하나로 통합한다.

    interface A1{
      public void styleA();
    
      // 메소드 시그니처가 같은 디폴트 메서드
      default public void styleSame(){
        System.out.println("A1 인터페이스의 디폴트 메서드 입니다.");
      }
    }
    
    interface B1{
      public void styleB();
    
      // 메소드 시그니처가 같은 디폴트 메서드
      default public void styleSame(){
        System.out.println("B1 인터페이스의 디폴트 메서드 입니다.");
      }
    }
    
    class MultiInterface implements A1, B1 {
      @Override
      public void styleA() {}
      @Override
      public void styleB() {}
    
      // 두 인터페이스 디폴트 메서드중 A1 인터페이스의 디폴트 메서드를 오버라이딩 하여 구현
      public void styleSame(){
        System.out.println("A1 인터페이스의 디폴트 메서드 입니다.");
      }
    }
    
    public class Main {
      public static void main(String[] args) {
        MultiInterface m1 = new MultiInterface();
        m1.styleSame(); // "A1 인터페이스의 디폴트 메서드 입니다."
      }
    }
  1. 인터페이스의 디폴트 메서드와 부모 클래스 메서드 간의 충돌

    • 이런 상황에서는 부모 클래스의 메서드가 상속되고 디폴트 메서드는 무시된다.
    • 만일 인터페이스 쪽의 디폴트 메서드를 사용할 필요가 있다면, 필요한 쪽의 메서드와 같은 내용으로 그냥 오버라이딩 해버리면 된다.
    interface A1{
    public void styleA();
    
    // C1 클래스와 메소드 시그니처가 같은 디폴트 메서드
    default public void styleSame() {
        System.out.println("A1 인터페이스의 디폴트 메서드 입니다.");
    }
    }
    
    abstract class C1 {
        // A1 인터페이스와 메소드 시그니처가 같은 인스턴스 메서드
        public void styleSame() {
            System.out.println("C1 클래스의 인스턴스 메서드 입니다.");
        }
    }
    
    // 메서드 시그니처가 같은 두 추상화들을 동시에 상속
    class MultiClassInterface extends C1 implements A1 {
        @Override
        public void styleA() {}
    }
    
    public class Main {
        public static void main(String[] args) {
            MultiClassInterface m1 = new MultiClassInterface();
            m1.styleSame(); // "C1 클래스의 인스턴스 메서드 입니다." - 클래스의 메서드 시그니처가 우선되어 적용됨
    
            // 마찬가지로 인터페이스 타입으로 다운캐스팅 해도 클래스 인스턴스 메서드로 호출 됨
            ((A1) m1).styleSame(); // "C1 클래스의 인스턴스 메서드 입니다."
        }
    }
    // 메서드 시그니처가 같은 두 추상화들을 동시에 상속
    class MultiClassInterface extends C1 implements A1 {
        @Override
        public void styleA() {}
    
        // 클래스의 인스턴스 메서드를 무시하고 인터페이스의 디폴트 메서드를 사용하기 위해 그대로 오버라이딩
        public void styleSame() {
            System.out.println("A1 인터페이스의 디폴트 메서드 입니다.");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            MultiClassInterface m1 = new MultiClassInterface();
            m1.styleSame(); // "A1 인터페이스의 디폴트 메서드 입니다."
        }
    }

static 메서드

  • 인스턴스 생성과 상관없이 인터페이스 타입으로 접근해 사용할 수 있는 메서드
  • 일반 클래스의 static 메소드와 다를 바 없다.

private 메서드

  • 자바9 버전에 추가된 메서드
  • 인터페이스에 default, static 메서드가 생긴 이후, 이러한 메서드들의 로직을 공통화하고 재사용하기 위해 생긴 메서드
  • private 메서드는 인터페이스 내부에서만 돌아가는 코드이다. (인터페이스를 구현한 클래스에서 사용하거나 재정의 할 수 없음)
  • 따라서 인터페이스 내부에서 private 메소드를 호출할때, default 메소드 내부에서 호출해야 하며,만일 private static 키워드를 붙인 메소드는 static 메소드에서만 호출이 가능하다. (클래스는 Static 메모리 영역에, 인스턴스 객체는 Heap 메모리 영역에 생성되기 때문)

인터페이스의 활용

인터페이스 다형성

  • 다형성의 법칙도 인터페이스에 그대로 적용이 가능하다.
    • 다형성: 같은 자료형에 여러 가지 객체를 대입하여 다양한 결과를 얻어내는 성질
  • 객체는 클래스가 아닌 인터페이스로 참조하라 라는 의미로 확장할 수 있다.
    • 적합한 인터페이스만 있다면 매개변수뿐 아니라 반환값, 변수, 필드를 전부 인터페이스 타입으로 선언하면 좋다.

      1. 객체는 인터페이스를 사용해 참조하라.
      2. 적당한 인터페이스가 있다면 매개변수뿐만 아니라 반환값, 변수, 필드를 전부 인터페이스 타입으로 선언하라.
      3. 객체의 실제 클래스를 사용할 상황은 '오직' 생성자로 생성할 때 뿐이다.
      4. 매개변수 타입으로는 클래스 보다는 인터페이스를 활용하라.

      인터페이스로 선언한 상황에서 다른 클래스로 변경하는 일이 생기는 경우 로직상의 문제가 없는지 반드시 확인해야 한다.

  • 다중 구현을 통한 자유로운 상속 관계를 만들어 클래스의 다형성보다 더욱 다채롭게 그리고 자유롭게 사용이 가능하다.

인터페이스 vs 추상클래스

인터페이스

  • 내부의 모든 메서드는 public abstract 로 정의 (default 메소드 제외)
  • 내부의 모든 필드는 public static final 상수
  • 클래스에 다중 구현 지원.
  • 인터페이스 끼리는 다중 상속 지원.
  • 인터페이스에도 static, default, private 제어자를 붙여 클래스 같이 구체적인 메서드를 가질 수 있음.
  • 따라서 하위 멤버의 중복 메서드 통합을 어느정도 할수는 있겠지만, 필드는 상수이기 때문에 중복 필드 통합은 불가능
  • 인터페이스는 부모 자식 관계인 상속에 얽매이지 않고, 공통 기능이 필요할 때마다 추상 메서드를 정의해놓고 구현(implement)하는 식으로 추상클래스보다 자유롭게 사용 가능
  • 인터페이스는 클래스와 별도로 구현 객체가 같은 동작을 한다는 것을 보장하기 위해 사용하는 것에 초점
  • 다중 구현이 된다는 점을 이용해, 내부 멤버가 없는 빈 껍데기 인터페이스를 선언하여 마커 인터페이스로서 이용 가능
  • 보통 xxxable 이런 형식으로 인터페이스 네이밍 규칙을 따름

추상 클래스

  • 추상클래스는 하위 클래스들의 공통점들을 모아 추상화하여 만든 클래스
  • 추상클래스는 다중 상속이 불가능하여 단일 상속만 허용한다.
  • 추상클래스는 추상 메서드 외에 일반클래스와 같이 일반적인 필드, 메서드, 생성자를 가질수 있다.
  • 이러한 특징으로, 추상클래스는 추상화(추상 메서드)를 하면서 중복되는 클래스 멤버들을 통합 및 확장을 할 수 있다.
  • 같은 추상화인 인터페이스와 다른점은, 추상클래스는 클래스간의 연관 관계를 구축하는 것에 초점을 둔다.

인터페이스를 사용하는 경우

  • 어플리케이션의 기능을 정의해야 하지만 그 구현 방식이나 대상에 대해 추상화 할 때
  • 서로 관련성이 없는 클래스들을 묶어 주고 싶을 때 (형제 관계)
  • 다중 상속(구현) 을 통한 추상화 설계를 해야할 때
  • 특정 데이터 타입의 행동을 명시하고 싶은데, 어디서 그 행동이 구현되는지는 신경쓰지 않는 경우
    • 구현 클래스에 의존하지 않고 인터페이스에 의존한다. (DIP(Dependency Inversion Principle)-의존 역전 원칙)
  • 클래스와 별도로 구현 객체가 같은 동작을 한다는 것을 보장하기 위해 사용

자유로운 타입 묶음

  • 상속에 구애받지 않은 상속(구현)이 가능하다는 것
  • 즉, 필요한 동작을 자유롭게 상속받아 설계할 수 있다.

인터페이스 다형성 이용 설계

  • 인터페이스는 필요에 따라 자유롭게 상속받아 사용할 수 있다.
  • 반면에 추상 클래스는 논리적인 클래스 상속 구조를 만들어두고 사용한다.

마커 인터페이스

  • 아무런 내용이 없는 빈 인터페이스를 선언하고 적절한 클래스에 implements 시킴으로서 단순한 타입 체크용으로 사용할 수 있다.
  • instanceOf 를 이용하여 마커 인터페이스를 상속받은 클래스인지 판단할 수 있다.

추상클래스를 사용하는 경우

  • 상속 받을 클래스들이 공통으로 가지는 메서드와 필드가 많아 중복 멤버 통합을 할때
    • 인터페이스는 상수 밖에 정의 못하기 때문에 중복 멤버 통합에 불리하다.
  • 멤버에 public 이외의 접근자(protected, private) 선언이 필요한 경우
  • non-static, non-final 필드 선언이 필요한 경우 (각 인스턴스에서 상태 변경을 위한 메소드가 필요한 경우)
  • 요구사항과 함께 구현 세부 정보의 일부 기능만 지정했을 때
  • 하위 클래스가 오버라이드하여 재정의하는 기능들을 공유하기 위한 상속 개념을 사용할 때
  • 추상 클래스는 이를 상속할 각 객체들의 공통점을 찾아 추상화시켜 놓은 것으로, 상속 관계를 타고 올라갔을 때 같은 부모 클래스를 상속하며 부모 클래스가 가진 기능들을 구현해야할 경우 사용한다.

추상클래스의 다형성 이용 설계

  • 추상클래스를 통한 다형성을 이용할 때에는, 부모 추상 클래스와 논리적으로 관련이 있는 확장된 자식 클래스들을 다룬다.

  • 이 점에서 클라이언트와 추상화 객체들은 의미적으로 관계가 묶여 있다.

  • 예시 코드

    public class ExamConsole {
    	Exam exam; // 상위 추상 클래스 타입으로 선언
        
        // 생성자 매개변수로 new NewlecExam() 혹은 new YBMExam() 생성자가 들어와 필드를 초기화
        ExamConsole(Exam e) {
        	this.exam = e; // 업캐스팅 초기화
        }
    
        void input() {}
        void print() {}
    }

명확한 계층 구조 추상화

  • 클래스끼리 명확한 계층 구조가 필요할 때도 추상클래스를 사용한다.
  • 추상클래스, 인터페이스 모두 추상 메소드를 이용한 구현 원칙을 강제한다는 점은 같지만, 추상클래스는 '클래스로서' 클래스와 의미있는 연관 관계를 구축할때 사용된다라고 보면 된다.

Interface VS Abstract Class

  • 인터페이스: 객체 생성을 위한 설계도
  • 추상 클래스: 객체들의 공통 특성들을 모아놓은 클래스

인터페이스는 확장성, 추상 클래스는 객체의 구조화 및 유지보수 용이성

Abstract Class Interface
사용 가능 변수 final, non-final, static, non-static variables static, final
사용 가능 접근 제어자 제한 없음 public
사용 가능 메소드 abstract method, non-abstract method abstract method
상속 키워드 extends implements
다중 상속 가능 여부 불가능 가능 ( ex. A class implements B, C )
사용 키워드 abstract interface
공통점 1. 인스턴스화 할 수 없다
➡ 인터페이스 혹은 추상 클래스를 상속받아 구현한 구현체의 인스턴스를 사용한다 (혹은 위와 같은 명령문을 사용할 경우 Anonymous Class를 사용하여 모든 메소드를 재정의 해야 한다)
2. 구현 여부에 관계 없이 선언 및 정의된 메소드 집합을 포함할 수 있다

질문 정리

Q1. 마커 역할은 abstract 클래스로는 불가능한가요?

A1. 추상 클래스도 내부가 비어있도록 설정할 수 있기 때문에, 하나의 마커만 사용한다면 사용할 수 있을 것입니다! 일반 클래스도 역시 마찬가지 입니다. 하지만 추상클래스나 클래스의 경우 다중 상속이 불가능하기 때문에 여러 개의 타입을 확인해야하는 경우나 마커를 상속받은 클래스가 다른 추상 클래스 또는 일반 클래스를 상속 받고 있는 경우에는 부적절하다고 생각합니다!


Q2. 마커 인터페이스와 어노테이션의 차이에 대해서 찾아보세요.

A2.

마커 인터페이스와 마커 어노테이션의 차이

  1. 마커 인터페이스를 구현한 클래스의 인스턴스를 구분하는 타입으로 쓸 수 있습니다. 마커 어노테이션은 구분하는 타입으로 사용할 수 없으며, 마커 어노테이션의 경우 런타임 시점에 발견할 오류를 마커 인터페이스를 구현하면 컴파일타임 시점에 발견할 수 있습니다.(ObjectOutputStream.writeObject 의 경우에는 런타임 시점에 문제를 확인하기 때문에 예외적으로 마커 인터페이스의 장점을 살리지 못한 케이스이다.)
  2. 마커 인터페이스는 적용 대상을 더 정밀하게 지정할 수 있습니다.
  • 마커 어노테이션 사용방법인 ElementType.Type으로 선언한 어노테이션의 제약조건을 걸게 되면 이 어노테이션이 붙은 모든 코드에 제약조건이 붙게된다.
  • 반면에, 마커 인터페이스는 마킹하고 싶은 특정 클래스에서만 마커 인터페이스를 구현하여 적용대상을 더 정밀하게 지정할 수 있다.
  1. 마커 어노테이션은 거대한 어노테이션 시스템의 지원을 받을 수 있습니다.

마커 인터페이스의 사용

  • 마킹된 객체를 매개변수로 받는 메서드를 작성해야할 때 (마커 인터페이스를 해당 메서드의 매개변수 타입으로 사용하면 컴파일타임에 오류 발생)
  • 새로 추가하는 메서드 없이 단지 타입 정의가 목적인 경우

마커 어노테이션의 사용

  • 클래스와 인터페이스 외의 프로그램 요소(모듈, 패키지, 필드 ,지역변수 등)에 마킹해야 할 때
    • 클래스와 인터페이스만이 인터페이스를 구현하거나 확장이 가능함.
  • 어노테이션을 활발히 활용하는 프레임워크를 사용할 때(예: 스프링 프레임워크)

Q3.

인터페이스는 결국 일종의 추상 클래스인가요? 추상 클래스와 인터페이스의 의도를 제외하고 코드만 놓고 봤을 때, 추상클래스에서는 선언 + 구현이 가능하고 인터페이스의 경우에는 선언만 가능하다는 차이가 있다고 알고 있습니다.

근데 자바 9 이후에 default 메서드를 허용했는데, 그러면 인터페이스에서도 선언과 구현이 같이 있는 것 아닌가요?

이 관점에서 둘의 차이가 무엇인지 궁금합니다. 만약 둘이 같지 않다면, 왜 자바 9에서 interface에 default 메서드를 허용했는지가 궁금합니다.

A3. Java8에서 default method가 등장하면서 추상 클래스와 인터페이스의 기능적인 명세가 비슷해진 것이 사실입니다!

추상 클래스에서 추상 메서드가 아닌 이미 구현된 메서드를 오버라이딩하지 않고 사용한다는 점에서 인터페이스의 defualt 메서드와 매우 흡사한 모습이기 때문입니다.

default 메서드가 등장한 이유는 ‘하위 호환성’이라는 개념 때문입니다. 간단히, 기존에 존재하던 인터페이스를 이용하여 이미 구현된 클래스들을 사용하고 있는 중, 인터페이스에 필수적인 메소드를 추가해야 할 경우를 떠올리면 이해하기 쉽습니다. default 메서드가 없는 경우 최상위 인터페이스에 새 메서드를 생성하게 되고 이로인해 이미 구현된 클래스와 호환성이 떨어지게 됩니다. 하지만 default 메서드를 추가하므로써 하위 호환성을 유지할 수 있게 되었습니다.

Reference