JAVA의 정석

(객체part2 ) 10. 인터페이스(1)

499.token.required 2023. 9. 23. 21:15

인터페이스는 일종의 추상클래스이다. 인터페이스는 추상클래스처럼 추상메서드를 갖고 있지만 추상클래스보다 추상화 정도가 높아, 추상

 

클래스와 달리 몸통을 갖춘 일반 메서드 또는 멤버변수를 구성원으로 가질 수 없다. 추상 클래스가 부분적으로만 완성된 '미완성 설계도' 라

 

한다면, 인터페이스는 밑그림만 그려져 있는 '기본 설계도'라 할 수 있다. 

 

 

인터페이스의 제약 사항 :

 

- 모든 멤버변수는 public static final 이어야 하며, 이를 생략할 수 있다.

 

- 모든 메서드는 public abstract 이어야 하며, 이를 생략할 수 있다. (단 static 메서드와 디폴트 메서드는 예외(JDK 1.8부터))

 

인터페이스에 정의된 모든 멤버에 '예외없이' 적용되는 사항이기 때문에 제어자를 생략할 수 있는 것이며, 편의상 생략하는 경우가 많다.

 

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

 

 

인터페이스의 특징

 

(1)  타입을 제외하고 항상 예외 없이 접근 제어자 생략이 가능하다. 

interface PlayingCard {

public static final int SPADE = 4 ;

final int DIAMOND = 3; // public static final int DIAMOND

static int HEART = 2; // public static final int HEART

int CLOVER = 1; // public static final int CLOVER

 

(2) 인터페이스의 조상은 인터페이스만 가능 (Object가 최고 조상이 아니다.)

 

(3) 인터페이스는 인터페이스로부터만 상속받을 수 있으며, 클래스와는 달리 다중 상속이 가능하다. 즉 여러개의 인터페이스로부터 상속을

 

받는 것이 가능하다. (추상메서드랑 메서드의 선언부가 동일해서 충돌해도 문제가 없다. 어차피 구현부가 없으니까.. )

 

 

 

인터페이스의 구현 

 

인터페이스도 추상클래스처럼 그 자체로는 인스턴스를 생성할 수 없으며, 추상클래스가 상속을 통해 추상메서드를 완성하는 것처럼,

 

인터페이스도 자신에 정의된 추상메서드의 몸통을 만들어주는 클래스를 작성해야 하는데, 그 방법은 추상클래스가 자신을 상속받는 클래스

 

를 정의하는 것과 다르지 않다. 다만 extends 와 implements의 차이를 둘 뿐이다.

 

 

인터페이스에 정의된 추상 메서드를 구현하는 법

 

class 클래스이름       implements   인터페이스이름 {

               // 인터페이스에 정의된 추상메서드를 모두 구현해야한다.

}

 

 

 

인터페이스를 이용한 다형성

 

자손클래스의 인스턴스를 조상타입의 참조변수로 참조하는 것이 가능하다는 것을 배운 것처럼 인터페이스 역시 이를 구현한 클래스의 조상

 

이라 할 수 있으므로, 해당 인터페이스 타입의 참조변수로 이를 구현한 클래스의 인스턴스를 참조할 수 있으며 인터페이스 타입으로의 

 

형변환 역시 가능하다.

 

인터페이스의 Fightable을 클래스 Fighter 가 구현했을 때, 다음과 같이 Fighter인스턴스를 Fightable 타입의 참조변수로 참조하는 것이 

 

가능하다 .

 

 

리턴타입이 인터페이스지만, 메서드의 실제 리턴은 fighter 객체이다.  이것은 인터페이스의 다형성에 의해서 인터페이스를 구현한 자손클

 

래스가 리턴된 것이다.  위에서 작성했 듯, 인터페이스는 객체를 만드는 것이 불가능하기 때문에 리턴타입이 인터페이스라면 그 인터페이스

 

를 구현한 자손객체들만 리턴할 수 있다는 것을 알아두자.  (실제로 Fighter f 객체가 반환됨. 참조변수 아님)

 

 

 

인터페이스의 장점 

 

 

(1) 개발시간을 단축 시킬 수 있다. 

 

일단 인터페이스가 작성되면 , 이를 사용패서 프로그램을 작성하는 것이 가능하다. 메서드를 호출하는 쪽에서는 메서드의 내용에 관계없이

 

선언부만 알면 되기 때문이다. 동시에 다른 한 쪽에서는 인터페이스를 구현하는 클래스를 작성하게하면 , 인터페이스를 구현하는 클래스가 

 

작성될 때 까지 기다리지 않고도 양쪽에서 병렬로 동시에 개발을 진행할 수 있다.

 

(2) 표준화가 가능하다.

 

프로젝트에 사용되는 기본틀을 인터페이스로 짠 다음, 개발자들에게 인터페이스를 구현하여 프로그램을 작성하도록 함으로써 일관되고 

 

정형화된 프로그램이 가능하다 .

 

(3) 서로 관계없는 클래스들에게 관계를 맺어 줄 수 있다. 

 

서로 상속관계에 있지도 않고, 같은 조상클래스를 가지고 있지 않은 서로 아무런 관계도 없는 클래스들에게 하나의 인터페이스를 공통적으

 

로 구현하도록 함으로써 관계를 맺어줄 수 있다. 

 

 

여기서 marine은 인간이고 , scv tank dropship은 기계이다.  위 처럼 기계를 수리하고 싶은 method를 만들고 싶을 때 , 

 

void repair(Tank t) {}

void repair(Dropship d) {}

 

일일이 유닛의 파라미터를 받아야 하니 다형성을 이용하려 한다. 하지만 다형성을 이용해서 scv와 tank클래스의 상위 클래스인

 

GroundUnit을 파라미터로 받자하니 Marine 클래스까지 상속받게 된다. 뿐만 아니라 dropshipd은 공중유닛이니 GroundUnit에 상속받

 

지 않아 난감하다. 이럴 경우처럼 아무 관계 없는 클래스들을 하나로 묶어주고 싶을 때 interface 를 사용하기 적절하다. 

 

 

 

구현한 모습

 

 

tank, dropship 등은 고칠 수 있는 Repairable 인터페이스 한 곳에 모았다. 이제 scv클래스의 repair 메서드에서 Repairable을 구현한

 

클래스만 파라미터로 받을 수 있을 것이고, 이 유닛이 Unit2의 객체라면 Unit2로 형변환하여, hp를 회복하는 수순을 밟을 것이다.

 

(여기서 했던 실수.. tank 클래스에서 int hp를 한 번 더 선언하는 바람에 t1.hp를 출력하였을 때 Unit u2 리모콘으로 조정하였던 hp가 출

 

력되지 않고 t1객체의 hp 가 출력되어 애먹었다.... 실질적으로 출력해야하는 hp는 탱크라는 유닛의 hp이다. hp를 중복반복해서 생긴 문제..)

 

(4) 선언과 구현을 분리함으로써 , 유연한 캡슐을 만들 수 있다 .

 

예를 들어 A라는 클래스의 메서드가, B의 메서드를 직접 호출한다고 가정해보자. 그렇다면 우선 A의 객체를 생성한 후 b의 객체를 a의 메

 

서드에  집어넣어 b의 함수를 호출하는 순서가 될 것이다. 

 

 

 

직관적인 예시

 

 

이런 관계를 A가 B에 의존한다고 말한다. 

 

이 경우에 만약 의존 관계를 변경하고 싶을 때, class A의 method 파라미터 부분, 몸통 부분을 전부 수정해야한다. 예를 들어 c에 의존하고 

 

싶을 때 , 파라미터도 C c , 메소드 호출도 c.methodC() 이런식으로 수정해야 할 것이다 .

 

 

하지만 이 경우에서 인터페이스라는 껍데기를 만들어 준다면 

 

 

껍데기와 알맹이를 분리

 

위와 달리 class A를 건들 필요 없이 메서드에서 interface를 구현한 자식 클래스의 객체만 생성해주면, B의 메서드 혹은 C의 메서드를

 

자유롭게 유동성 있게 호출해 줄 수 있다.