인터페이스
- 추상메서의 집합이라는 것이 핵심이자 본질이다.
- 구현된 것이 없는 선언부만 존재한다.
- 이름이 인터페이스인만큼 모든 멤버가 public이다.
- 추상 클래스와의 차이?
- 추상 클래스는 단지 "일반 클래스가 추상 메서드를 멤버로 가지고 있으면 추상 클래스"이다.
(생성자, 멤버변수 등 다양하게 가질 수 있음) - 인터페이스는 "추상메서드의 집합"이다.
- 추상 클래스는 단지 "일반 클래스가 추상 메서드를 멤버로 가지고 있으면 추상 클래스"이다.
인터페이스 선언
interface PlayingCard{
// 상수
public static final int SPADE = 4;
final int DIAMOND = 3;
static int HEART = 2;
int CLOVER = 1;
// 추상 메서드
public abstract String getCardNumber();
String getCardKind();
}
- 인터페이스에 변수는 올 수 없고, 무조건 상수만 올 수 있으므로
public, static, final 등을 입력하지 않아도 자동으로 상수로 인지한다. - 인터페이스에 추상메서드만 올 수 있으므로,
public, abstarct를 생략해도 자동으로 public 추상메서드 선언부로 인지한다.
인터페이스의 상속
- 인터페이스의 조상은 인터페이스만 가능하다.
(때문에 Object클래스는 인터페이스의 조상이 아니다.) - 다중 상속이 가능하다.
- why?
클래스의 다중상속이 안되는 근본적인 이유는 메서드의 충돌문제(메서드의 이름은 같고, 구현은 다를 수 있음)이다.
그런데, 인터페이스의 경우에는 내부에 선언부만 존재하고, 구현부는 존재하지 않는다.
때문에 메서드의 이름이 동일하다고 해도 전혀 문제가 되지 않기 때문이다.
- why?
인터페이스의 구현
- 인터페이스에 정의되어 있는 추상 메서드를 구현하는 것이다.
interface Fightable{
void move(int x, int y);
void attack(Unit u);
}
class Fighter implements Fightable{
public void move(int x, int y){
...
}
public void attack(Unit u){
....
}
}
- Fighter 클래스는 Fightable 인터페이스를 구현한 것이다.
- 여기서 내가 놓쳤던 포인트는 인터페이스를 구현하는 메서드에서 메서드를 구현할 때는 public키워드를 붙어야 한다.
왜냐하면 interface는 모든 메서드가 public 접근제어자인데, 하위 클래스는 상위 클래스의 접근 범위보다 좁아야 하기 때문이다. - 추가로, 만약 클래스에서 인터페이스의 일부 메서드만 구현한다면?
- 추상클래스와 마찬가지로 abstract 키워드를 꼭 붙어줘야 한다.
(모든 추상메서드를 구현한 것이 아니기 때문에 아직 추상클래스이다.)
- 추상클래스와 마찬가지로 abstract 키워드를 꼭 붙어줘야 한다.
인터페이스와 다형성
- 인터페이스는 클래스의 다중상속이 불가능한 문제를 해결해주는 역할이 되기도 한다.
- 근본적인 이유인 충돌문제를 해결했기 때문이다.
- 그렇다면 구현 클래스의 부모 역할은 한다고 했을 때, 당연히 다형성이 가능하다.
class Fighter extends Unit implements Fightable{
public void move(int x, int y){
...
}
public void attack(Fightable f){
...
}
}
.
.
Unit u = new Fighter();
Fightable f = new Fighter();
- 인터페이스와 클래스를 상속받은 Fighter 클래스가 있다.
- 추상 클래스와 마찬가지로 맨 아래 코드처럼 인터페이스도 다형성이 가능하다.
- Fightable 인터페이스로 자손 인스턴스를 참조하는 것이 가능.
- 사용할 수 있는 멤버는 Fightable인터페이스에 선언된 추상 메서드만 사용 가능.
- Fighter 클래스에서 attack 메서드를 구현한 것을 보아하니,
Fightable인터페이스에서 attack메서드의 매개변수는 인터페이스 타입이었다는 것을 예측할 수 있는데, 여기서 중요한 것은
매개변수 타입이 인터페이스라는 것은 들어올 수 있는 인자가 해당 인터페이스를 구현한 클래스의 인스턴스만 가능한 것이다.
즉 위 예제에서는 f.attack(new Fighter()); 라는 코드로써 attack을 사용할 수 있는 것. - 인터페이스 다향성의 기나긴 예제
abstract class Unit{
int x,y;
abstract void move(int x, int y);
void stop() {System.out.println("stop")}
}
interface Fightable{
void move(int x, int y);
void attack(Fightable f)
}
class Fighter extends Unit implements Fightable{
public void move(int x, int y){
System.out.println(x+""+ "," + y+""+ "로 이동");
}
public void attack(Fightable f){
System.out.println(f+"를 공격");
}
}
public class InterfaceEx {
public static void main(String[] args) {
Fighterable f = new Fighter();
f.move(300,200);
f.attack(new Fighter());
}
}
- main 메서드 내부에 3줄을 보고 다형성의 흐름을 이해할 수 있다.
인터페이스의 장점
인터페이스의 장점은 크게 두 가지가 있다.
- 의존적인 관계의 클래스들을 간접적인 관계로 변경할 수 있다.
- 서로 관계없는 클래스들을 관계 맺어줄 수 있다.
- (+ 표준화가 가능한 장점도 있다.)
1. 선언과 구현을 분리시킬 수 있다.
- 선언과 구현을 분리시키는 인터페이스 덕분에 두 개의 클래스를 의존적인 관계에서 간접적인 관계로 바뀌어,
코드가 유연해지고, 변경에 유리해진다. - 아래 두 가지 예를 살펴보자.
1-1. 직접적인 관계의 두 클래스(A - B)
class A {
public void method(B b){
b.method();
}
}
class B{
public void method(){
System.out.println("class B");
}
}
class C{
public void method(){
System.out.println("class C");
}
}
public class InterfaceEx{
public static void main(){
A a = new A();
a.method(new B());
}
}
- 만약 위 코드처럼 A클래스가 B클래스에 의존적이라면,
A클래스가 C클래스를 호출하고 싶을 때는 A클래스의 method(B b)를 method(C c)로 변경을 해줘야 한다. - 변경에 불리하다.
1-2. 간접적인 관계의 두 클래스(A - I - B)
class A {
public void method(I i){
i.method();
}
}
interface I {
void method();
}
class B implements I {
public void method(){
System.out.println("class B");
}
}
class C implements I {
public void method(){
System.out.println("class B");
}
}
public class InterfaceEx{
public static void main(){
A a = new A(new B());
a.method();
}
}
- A가 B에 접근하려고할 때, 가운데 인터페이스가 존재하는 느낌이다.
- 위와 같이 인터페이스를 적용하여 선언부와 구현부를 나누어 준 덕분에,
A가 C에 접근하고 싶을 때는 A 클래스를 수정할 필요가 없어지게 된다. - 변경에 유리한 유연한 설계가 가능하게 된다.
2. 서로 관계없는 클래스들을 관계 맺어줄 수 있다.
- 아래 이미지와 같은 상속계층도가 있다고 가정해보자.
- 위 이미지에서 SCV, Tank, Dropship이라는 3개의 클래스간의 관계를 맺고 싶다면 어떻게 해야할까?
- 만약 이 3개의 클래스들이 '수리'라는 작업을 받기 위해서 'repair'라는 클래스로 묶고싶다면?
- 오버로딩
void repair(Tank t){ ... }
void repair(SCV s){...}
void repair(Dropship d){...}
- 위 처럼 오버로딩을 통해 구현할 수 있지만, 당연히 좀 아닌거같다는 생각이 든다..
2. 인터페이스 사용
interface Repairable {}
class SCV extends GroundUnit implements Repairable{...}
class Tank extends GroundUnit implements Repairable{...}
class Dropship extends AirUnit implements Repairable{...}
- 위 처럼 interface로 Repairable이라는 것을 만들고, 원하는 클래스마다 해당 인터페이스를 구현한 클래스로 만들어주면
Repairable이라는 인터페이스 내에 선언된 추상 메서드를 구현할 수 있게 된다.
'JAVA' 카테고리의 다른 글
3. 예외와 예외처리 (0) | 2022.12.15 |
---|---|
1. 자바 컴파일 과정 (0) | 2022.11.23 |
21. 추상 클래스, 추상 메서드 (0) | 2022.11.11 |
20. 다형성의 장점 (0) | 2022.11.10 |
19. 다형성, 참조변수의 형변환 (0) | 2022.11.10 |