Language/Java

[Java] 자바 인터페이스(interface), 추상 메소드

KAispread 2022. 5. 30. 16:08
728x90
반응형

자바의 인터페이스는 객체 지향의 핵심 개념인 추상화를 가장 잘 나타내는 개념이다. 설계시 인터페이스를 먼저 작성하면 어떠한 메소드를 구현해야 하는지, 매개 변수의 이름을 어떻게 할지를 일일이 고민하지 않아도 된다. 따라서, 어느정도 정형화된 개발이 가능해진다. 그리고 인터페이스와 같이 구현의 성질을 가지는 추상 메소드도 함께 활용한다면 선언과 구현을 구분하여 개발할 수 있다.

또한, 구체 클래스가 아니라 인터페이스의 개념인 추상화에 의존하게 함으로써 여러가지 이점을 얻을 수 있다.

 


 

인터페이스(Interface)

인터페이스 선언 방법

인터페이스의 선언 방법은 다음과 같다.

public interface 인터페이스명{
}

다음과 같이 public interface "인터페이스명"으로 선언하면 된다.

인터페이스 선언 시 클래스와 다른 가장 큰 특징은 다음과 같다.

  • 메소드선언 시 몸통이 없어야 한다. (Java 8 이전)
  • 변수를 선언할 수 있다. 단, 이후 수정이 불가능한 final로 선언되며 선언과 동시에 초기화 해주어야 한다.
public interface Ballad {
   public final int a = 3;
   public String singingType();
   public void drinkWater();
}

위의 코드에서 볼 수 있듯, 메소드는 선언부를 제외하고 중괄호 { }로 묶여진 코드가 없어야 한다.(이를 메소드의 몸통이 없다고 한다)
또한, 변수는 선언과 동시에 초기화가 이루어져야 한다.

 

인터페이스 상속 방법

인터페이스를 클래스에서 상속받는 방법은 implements(구현하다)라는 키워드를 이용하는 것이다.

인터페이스를 상속받을 때는 다음과 같이 인터페이스에 선언된 메소드들을 필수적으로 구현해야한다(Override). 구현하지 않는다면 컴파일오류가 발생한다. (인터페이스에 선언된 변수는 상속받은 클래스에서 꼭 사용하지 않아도 된다.)

public interface Ballad {
   public final int a = 3;
   public String singingType();
   public void drinkWater();
}

// implements Interface

public class Singer implements Ballad {
   @Override
   public String singingType() {
      return "Ballad";
   }
   
   @Override
   public void drinkWater() {
      System.out.println("He is drinking water");
   }
}

 

 

인터페이스끼리 상속도 가능하다.

다음과 같이 인터페이스 끼리 상속도 가능하다.

public interface Practice {
   public void getMember();
   public void reservationStudio();
}
// interface

public interface Ballad extends Practice{ //Practice 인터페이스를 상속
   public final int a = 3;
   public String singingType();
   public void drinkWater();
}
// interface를 상속 

public class Singer implements Ballad{
   @Override
   public String singingType() {
      return "Ballad";
   }
   @Override
   public void drinkWater() {
      
   }
   @Override
   public void reservationStudio() {
      
   }
   @Override
   public void getMember() {
      
   }
}

인터페이스끼리의 상속은 extends 키워드를 통해 가능하다. Ballad 인터페이스는 Practice 인터페이스를 상속받았기 때문에 실질적으로 4개의 추상 메소드가 존재한다. 따라서, Ballad 인터페이스를 상속받은 Singer 클래스에서는 총 4개의 메소드를 Override 하여야한다.


 

인터페이스를 왜 사용하는가?

1. 다중 상속

인터페이스의 가장 큰 장점은 다중 상속이 가능하다는 점이다.
클래스 상속은 하나의 클래스만 가능하지만, 인터페이스는 필요에 따라 여러개의 인터페이스를 상속받아, 클래스를 구현할 수 있다. (인터페이스는 기본적으로 메소드의 선언부만 존재하므로 다이아몬드 문제의 모호함이 발생하지 않는다.)

다이아몬드 문제?
다중 상속시 부모 클래스에 같은 시그니처(메소드명, 리턴값, 매개변수 등)를 가진 메소드가 존재할 때, 어떤 메소드를 상속받아야 하는지 판별할 수 없는 문제이다.

 

public interface Ballad {
   public final int a = 3;
   public String singingType();
   public void drinkWater();
}
// Interface

public interface Practice {
   public void getMember();
   public void reservationStudio();
}
// Interface

public class Singer implements Ballad, Practice{
   @Override
   public String singingType() {
      return "Ballad";
   }
   @Override
   public void drinkWater() {
      System.out.println("He is drinking water");
   }
   @Override
   public void getMember() {
      System.out.println("Getting member");
   }
   @Override
   public void reservationStudio() {
      System.out.println("Studio reservation is complete");
   }
}

인터페이스를 다중상속하는 방법은 간단하게 implements "인터페이스명" 뒤에 콤마, 로 다른 인터페이스를 붙여주면 된다. 당연하게도 상속받은 인터페이스의 모든 메소드를 구현해야 한다.

다음과 같이 클래스 상속과 인터페이스 상속을 같이 할 수도 있다.

public interface Ballad {
   public final int a = 3;
   public String singingType();
   public void drinkWater();
}
// Interface

public interface Practice {
   public void getMember();
   public void reservationStudio();
}
// Interface

public class Artist {
   public String name;
}
// Super Class

public class Singer extends Artist implements Ballad, Practice{
   @Override
   public String singingType() {
      return "Ballad";
   }
   @Override
   public void drinkWater() {
      System.out.println("He is drinking water");
   }
   @Override
   public void getMember() {
      System.out.println("Getting member");
   }
   @Override
   public void reservationStudio() {
      System.out.println("Studio reservation is complete");
   }
}

클래스 상속과 인터페이스 상속이 같이 이루어질때는 extends 키워드를 먼저 써주고 뒤에 implements가 와야한다.
이와 같이, 인터페이스를 통해 상속받은 클래스가 어떤 기능(메소드)이 있어야하는지를 정해줄 수 있어, 개발 시간이 단축된다.

 

 

2. 다형성

또한, 이렇게 여러개의 인터페이스와 클래스를 상속받음으로써 객체의 형태를 다양하게 할 수 있는데, 코드를 통해 살펴보자.

public class ArtistManager {

   public static void main(String[] args) {
      ArtistManager sample = new ArtistManager();
      
      Singer sing = new Singer();
      Artist art = new Singer();
      Ballad ballader = new Singer();
   }
}

위에서 본 Singer라는 클래스는 Artist라는 클래스도 상속받고 있고, Ballad, Practice라는 인터페이스까지 상속받고 있다. 따라서, Singer라는 객체의 자료형은 자신의 자료형인 Singer 포함, Artist, Ballad, Practice가 될 수 있는 것이다.
이와 같은 다형성은 자료형이 제한되어있는 자료 구조(ex 배열)에서 유용하게 사용될 수 있다.
각 자료형으로 선언하였을때의 제약은 다음과 같다.

  • Singer : Artist, Ballad, Practice에 선언된 모든 메소드와 변수 + Singer에만 선언된 변수, 메소드 사용 가능
  • Artist : Artist에서 선언된 변수, 메소드만 사용 가능
  • Ballad: Balad에서 선언된 변수, 메소드만 사용 가능
  • Practice: Practice에서 선언된 변수, 메소드만 사용 가능


아래 코드에서와 같이, 메소드 Overloading을 이용하여 매개변수로 받은 클래스가 어떤 인터페이스를 상속받았는지에 따라, 다르게 동작하는 메소드도 만들 수 있다.

public interface Ballad {
}
public class Singer implements Ballad{
}
// Ballad interface 상속

public interface Rap {
}
public class Rapper implements Rap{
}
// Rap interface 상속

public class ArtistManager {
   public static void main(String[] args) {
      ArtistManager sample = new ArtistManager();
      
      Singer sing = new Singer();
      Rapper rap = new Rapper();     
      sample.greetingArtist(sing); //Hello!! Ballader 출력
      sample.greetingArtist(rap); //Hello!! Rapper 출력
   }
   
   public void greetingArtist(Ballad artist) {
      System.out.println("Hello!! Ballader");
   }
   
   public void greetingArtist(Rap artist) {
      System.out.println("Hello!! Rapper");
   }
}

 

 

- JAVA 8 이후 -

인터페이스 default 메소드

자바 8 버전 이후부터 디폴트 메소드를 사용할 수 있다. 기존 인터페이스에 선언된 메소드는 몸통이 없고 선언부만 존재해야하지만, 디폴트 메소드는 일반 메소드처럼 선언할 수 있다.

public interface Ballad {
   public final int a = 3;
   public String singingType();
   public void drinkWater();
   
   default void printSingType() {  //default method
      System.out.println("Ballad");
   }
}

다음과 같이 메소드 선언 시, 앞에 default 키워드를 붙여주면 된다.
메소드의 몸통이 존재하기 때문에 상속받은 클래스에서 해당 메소드를 반드시 Overriding 할 필요는 없다. 그리고 접근 제어자는 public 이다.

이미 작성된 인터페이스에서 기능을 추가하려고 할 때, 구현체들을 전부 Overriding 해야하는 번거로움을 막기 위해 사용하는 것이 목적이다.


 

인터페이스 static 메소드

마찬가지로 자바 8버전부터 스태틱 메소드를 사용할 수 있다. 다른 static 사용법과 동일하게 별도의 객체 생성없이 메소드를 실행시킬 수 있다. 인터페이스의 static 메소드도 default 메소드처럼 몸통을 가질 수 있다.

default 메서드와 다른점은 static 메서드는 Override가 불가능하다는 것이다.

자바는 static, final, private 메서드는 오버라이드가 불가능하다.

public interface Ballad {
   public final int a = 3;
   public String singingType();
   public void drinkWater();
   
   static int getA() {  //static method
      return a;
   }
}

이와 같이 선언하면 다른 클래스에서 Ballad.getA() 코드로 인터페이스의 스태틱 메소드를 실행시킬 수 있다.

 

추상 클래스

위에서 살펴본 인터페이스는 기본적으로 선언된 모든 메소드가 전부 몸통이 없어야 한다. (디폴트, 스태틱 메소드 제외)
하지만 추상 클래스는 기본적으로 클래스의 위치에서, 인터페이스처럼 몸통이 없는 메소드도 추가할 수 있는 클래스이다.

  • 인터페이스 : 몸통이 없는 메소드만 선언 가능(메소드의 선언부만 존재)
  • 추상 클래스 : 클래스의 모든 특징을 가지고 있지만, 몸통이 없는 메소드가 존재 할 수 있는 클래스. (즉, 몸통이 있는 메소드를 포함해도 된다)

 

 

추상 클래스 선언 방법

추상 클래스는 다음과 같이 class 앞에 abstract 키워드를 써주면 된다. (abstract : 추상적인)

public abstract class Artist {
   public String name;
   
   public Artist(){   
   }
   
   public abstract void printScore();
   // abstract method
   
   public void printEnt() {
      System.out.println("My Entertainment is SM");
   }
}

추상 클래스는 추상 메소드를 가질 수 있다는 점을 제외하고 일반 클래스와 완전히 동일하다.

추상 메소드 선언 시 리턴값 앞에 abstract 키워드를 적어주어야 하며, abstract로 선언된 클래스에서만 선언할 수 있다.

이 추상 메소드를 상속받는 방법도 일반 클래스를 상속받는 방법과 동일하게 extends 키워드를 사용한다.

public class Singer extends Artist{
   @Override
   public void printScore() {
      
   }
}

하지만 몸통이 없는 추상 메소드를 상속받기 때문에 상속받은 클래스에서는 필수적으로 추상 메소드를 Overriding 해주어야 한다.

 

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

인터페이스와 추상클래스의 차이점은 다음과 같다.

  인터페이스 추상 클래스 클래스
선언 시 키워드 interface abstract class class
추상 메소드 포함 가능 여부 O O X
구현된 메소드 포함 가능 여부 X O O
상속(extends) O(인터페이스끼리만 가능) O O

 

 

결론

어떤 객체를 사용할 때, 상속관계를 살펴보는 것은 매우 중요하다. 그 클래스의 코드를 살펴보지 않아도, 상속받은 클래스와 인터페이스를 통해 어떤 기능이 있는지 짐작 가능하기 때문이다. List 자료구조로 가장 많이 사용되는 ArrayList 의 상속관계를 보자.

Oracle 공식문서에 나와있는 ArrayList 상속관계


다음과 같이 ArrayList 에는 AbstractCollection<E>, AbstractList<E>클래스와 Serializable, Cloneable, Iterable<E>, Collection<E>, List<E>, RandomAccess 인터페이스를 구현하고 있다. 이를 통해, "이 클래스는 List의 구조를 가지면서 Cloneable 인터페이스를 구현하고 있으니, 이 객체는 복제 기능의 메소드가 있겠구나" 라고 코드를 보지 않더라도 생각할 수 있다는 것이다.

 


추가적으로, 인터페이스와 추상클래스를 언제 구분해서 사용해야할지 Oracle 공식 튜토리얼 문서에서는 다음과 같이 설명하고 있다.

 

추상클래스

  • 밀접하게 연관된 클래스끼리 코드를 공유해야 할 때
  • 추상 클래스의 하위 클래스들이 공통된 필드나 메소드를 많이 공유하고, 접근제어자가 public 이 아닌 경우
  • Non-static 이거나 non-final 필드로 객체의 상태를 바꿔야 하는 경우


인터페이스

  • 연관되지 않은 클래스들끼리 관계를 맺어줄 때
  • 특정 데이터 타입의 동작을 지정하려고 하지만 해당 동작을 누가 구현하는지는 중요치 않을 때
  • 다중 상속이 필요할 때


추상 클래스와 인터페이스를 둘 다 상속받는 클래스도 존재하는데, 대표적으로 Collection의 AbstractList, AbstractMap등이 있다.




참고
ㆍ"자바의 신 vol.1", 이상민 저자
ㆍhttps://wikidocs.net/217
728x90
반응형