자바의 정석 ch7.7 인터페이스(interface)

해로(haero77)·2022년 2월 3일
2

Java 문법

목록 보기
4/15
post-thumbnail

📔 본 포스팅은 자바의 정석(남궁성 저, 3판)을 읽고 정리한 글입니다.

7.1 인터페이스란 추상메서드의 집합이다

  • (결론) 💯💯💯 인터페이스는 추상 메서드의 집합임 (추상 클래스의 일종)
  • (핵심) 구현된 것이 전혀 없는 설계도. 껍데기(모든 멤버가 public)

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

  • 추상 클래스는 클래스인데 추상 메서드를 포함하는 클래스, 멤버 변수를 가질 수 있음.
  • 인터페이스는 추상 메서드’만’ 포함하는 클래스, 멤버 변수(iv, cv)를 가질 수 없음
    • 정확히 말하자면 추상 메서드외에도 상수, static메서드, default메서드를 가질 수 있으나, 핵심은 인터페이스가 추상 메서드의 집합이라는 것임

7.2 인터페이스의 작성

interface 인터페이스이름 { // 모든 멤버가 public
	public static final 타입 상수이름 =; // 상수는 가질 수 있으나 변수(iv, cv)는 가질 수 없음
	public abstract 메서드이름(매개변수 목록); // 추상 메서드
}
interface PlayingCard {
	public static final int SPADE = 4; 
	final int DIAMOND = 3; // public static final int DIAMOND = 3;
	static int HEART = 2; // public static fianl int HEART = 2;
	int CLOVER = 1;  // public static final int CLOVER = 1;

	public abstract String getCardNumber();
	String getCardKind(); // public abstract String getCardKind();
}
  • 인터페이스의 모든 멤버는 public 임 (생략 가능)
  • 상수 선언에서 public, final, static 은 생략 가능.
  • 추상 메서드 선언에서 public, abstract 은 생략 가능.

7.3 인터페이스의 상속

  • 인터페이스의 조상은 인터페이스만 가능(Objcet가 최고 조상이 아님)
  • 인터페이스는 다중 상속이 가능(추상 메서드는 충돌해도 문제없음, 선언부만 있으니까)
    • 추상 클래스는 단일 상속만 가능(메서드의 선언부가 같고 구현부가 다르면 어느 쪽을 상속받을지 결정 불가능하므로)
interface Movable {
	/** 지정된 위치(x, y)로 이동하는 기능의 메서드*/
	void move(int x, int y); // public abstract 생략
}

interface Attackable {
	/** 지정된 대상(u)을 공격하는 기능의 메서드 */
	void attack(Unit u);
}

interface Fightable extends Movable, Attackable {} // 멤버 2개 

7.4 인터페이스의 구현

  • 인터페이스의 정의된 추상 메서드를 완성하는 것(=구현)
  • 인터페이스에 정의된 추상 메서드를 모두 구현해야함
class 클래스이름 implements 인터페이스이름 {
	// 인터페이스에 정의된 추상메서드를 모두 구현
}
interface Fightable {
	void move(int x, int y); // public abstract
	void attack(Unit u);
}

class Fighter implements Fightable {
	public void move(int x, int y) { /* 내용 생략 */ }
	public void attack(Unit u) { /* 내용 생략 */ }
}
  • 구현하는 인터페이스의 메서드 중 일부만 구현한다면, abstract 를 붙여 추상 클래스로 선언한다.
abstract class Fighter implements Fightable {
	public void move(int x, int y) { /* 내용 생략 */ }
	// 멤버로 'public abstract vboid attack(Unit u);' 를 가짐
}

7.5 인터페이스를 이용한 다중 상속

  • 자바는 다중 상속을 허용하지 않는 언어이나, 인터페이스를 통해 다중 상속이 가능 하기야 하다.(실제로 인터페이스로 다중 상속을 하는 경우는 거의 없다)
  • 두 개의 클래스로부터 상속을 받아야하는 상황이라면
    1. 두 조상클래스 중에서 비중이 높은 쪽을 선택하고 다른 한쪽을 클래스 내부에 멤버로 포함시키는 방식으로 처리하거나
    2. 어느 한쪽의 필요한 부분만 뽑아서 인터페이스로 만든 다음 구현하도록 한다.

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

  • 인터페이스도 구현 클래스의 조상 개념으로 본다.
  • 인터페이스 타입의 참조변수로 자손 객체를 참조가능함(다형성 개념)
class Fighter extends Unit implements Fightable {
	public void move(int x, int y) { /* 내용 생략 */ }
	public void attack(Unit u) { /* 내용 생략 */ }
}

Unit u = new Fighter(); // OK
Fightable f = new Fighter(); // OK

💯💯💯인터페이스 타입 매개변수의 사용은 해당 인터페이스를 구현한 클래스의 객체만 가능

interface Fightable {
	void move(int x, int y);
	void attack(Fightable f); // Fightable인터페이스 구현한 놈들만 와라 
 }
  • 위 코드에서 attack()메서드는 Fightable인터페이스를 구현한 클래스의 객체로만 매개변수를 전달가능함(호출하기 위해서는 Fightable인터페이스를 구현한 클래스의 객체가 있어야함)

리턴 타입이 인터페이스라는 것은 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것을 의미한다.

Fightable method() { // 리턴 타입이 Fightable인터페이스 = Fightable 인터페이스를 구현한 클래스의 인스턴스를 반환
	...
	Fighter f = new fighter(); // 아래 문장을 포함하여 'return new Fighter'로 사용 가능
	// 반환 타입이 Fightable이어야하지만, f는 Fightable로 형변환이 가능하기 때문에 이렇게 사용 가능함
	return f; // '(Fightable)' 생략됨(자동 형변환)
}

...

class Fighter implements Fightable {
	public void move(int x, int y) { /* 내용 생략 */ }
	public void attack(Unit u) { /* 내용 생략 */ }
}
  • 메서드의 리턴 타입이 인터페이스라는 것의 의미
    • 💯💯💯 해당 인터페이스를 구현한 클래스의 인스턴스(의 주소)를 리턴함
/* 인터페이스 타입의 매개변수, 인터페이스 리턴타입의 메서드 사용 예제 */
abstract class Unit2 {
    int x, y;

    abstract void move(int x, int y);

    void stop() {
        System.out.println("멈춥니다");
    }
}

interface Fightable {
    void move(int x, int y); // public abstract 생략됨
    // 인터페이스 타입 매개변수 사용
    void attack(Fightable f); // public abstract 생략됨
}

class Fighter extends Unit2 implements Fightable {
    // 오버라이딩 규칙 : 조상(public)보다 접근제어자가 범위가 좁으면 안된다.
    public void move(int x, int y) {
        System.out.println("[" + x + "," + y + "]로 이동");
    }

    public void attack(Fightable f) {
        System.out.println(f + "를 공격");
    }

    // 싸울 수 있는 상대를 불러온다.
    Fightable getFightable() {
        Fighter f = new Fighter(); // Fighter를 생성해서 반환, '(Fightable)' 생략됨
        return f; 
    }
}

public class FighterTest {
    public static void main(String[] args) {
        Fighter f = new Fighter();
        // getFightable()의 리턴타입이 Fightable인터페이스 이기 때문에, 이를 저장하는 참조변수의 타입 또한 Fightable타입
        Fightable f2 = f.getFightable();
        
    }
}

7.7 💯인터페이스의 장점

인터페이스의 개념적 의미

  • 인터페이스는 두 객체 간의 ‘연결, 대화, 소통’을 돕는 ‘중간 역할’을 한다.
    • 인터페이스의 예 : 윈도우 GUI
      • 내부 알맹이(데스크탑 부품)이 바뀌어도 껍데기(Graphic User Interface)만 안 바뀌면 그대로 사용이 가능하다(=변경에 유리)

장점 1. 개발시간을 단축시킬 수 있다.

  • 일단 인터페이스가 작성되면 이를 사용해서 프로그램을 작성하는 것이 가능함. (메서드를 호출하는 곳에서는 선언부만 알면 되므로)

장점 2. 표준화가 가능하다.

  • Oracle DB가 가격이 오르면서 MySQL DB를 이용해야 하는 상황이 생김. 이 때 인터페이스가 없으면 수많은 자바 코드들을 변경해야 했을 것임.
    • 자바 애플리케이션과 DB사이에 JDBC라는 인터페이스 집합을 둠으로써,
      (애플리케이션 - JDBC - DATABASE) 의 간접관계가 형성됨.
    • 이 JDBC가 바로 ‘표준’에 해당.

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

  • (문제 상황) 위의 상속 계층도에서 SCV, Tank, Dropship 클래스만 사용가능한 repair()메서드를 만들고 싶은데, 아래와 같이 GroundUnit타입의 매개변수를 쓰자니 Marine클래스도 repair() 메서드를 이용하게 되어 원치 않은 결과를 얻게됨
void Repair(Tank t) {
	// Tank를 수리한다
}

void Repair(Dropship d) { // 오버로딩
	// Dropship을 수리한다
}

// 과도한 오버로딩을 방지하기위해 조상 클래스 타입 매개변수를 이용
void Repair(GroundUnit gu) {
	// 매개변수로 넘겨진 GroundUnit을 수리한다
}
  • 해결 : 아래 그림 처럼 공통점을 인터페이스로 따로 빼서 만들고, 각 클래스가 이를 구현하게 만듦

interface Repairable {}

class SCV extends GroundUnit implements Repairable { }

class Tank extends GroundUnit implements Repairable { }

class Dropship extends AirUnit implements Repairable { }

// 실제 메서드 구현시
void Repariable(Repairable r) { // Repairable인터페이스를 구현한 클래스의 객체만 매개변수로 가능
	if (r instanceof Unit) {
		Unit u = (Unit) r;
		while (u.hitPoint != u.Max_HP) {
			u.hitPoint++;
		}
	}
} // repair(Repairable r)

장점 4. 독립적인 프로그래밍이 가능하다.

  • 인터페이스를 이용하면 클래스의 선언과 구현을 분리시킬 수 있기 때문에 실제 구현에 독립적인 프로그램을 작성하는 것이 가능함.
// 껍데기와 알맹이가 같이 있는 클래스
class B { // 껍데기 + 알맹이. 유연하지 않음 & 변경에 불리
	public void method() {
		System.out.println("method in B");
	}
}

// 껍데기와 알맹이로 분리, 유연함 & 변경에 유리
Interface I { // 껍데기
	public void method();
}

class B implements I{ // 알맹이 (B클래스)
	public void mehtod() {
		System.out.println("method in B");
	}
}
  • 클래스와 클래스 간의 직접적인 관계를 인터페이스를 이용해서 간접적인 관계로 변경하면, 한 클래스의 변경이 관련된 다른 클래스에 영향을 미치지 않는 독립적인 프로그래밍이 가능함(느슨한결합)

7.8 인터페이스의 이해(본질)

  • 인터페이스는 실제 구현 내용(인터페이스를 구현한 클래스)을 감싸고 있는 껍데기이며, 인터페이스와 관계된 클래스는 직접적인 관계에 있는 인터페이스에만 영향을 받게 된다. (연결된 클래스는 인터페이스 안에 어떤 알맹이가 들어있는지는 몰라도 된다)

// 직접적인 관계의 두 클래스 (A-B)
class A {
	public void mehtodA(B b) {
		b.methodB();
	}
}

class B {
	public void mehtodB() {
		System.out.println("methodB()");
	}
}

class InterfaceTest {
	public static void main(String[] args) {
		A a = new A();
		a.methodA(new B());
	}
}
  • 위 코드를 수정하여 아래 처럼 클래스 A와 B사이에 인터페이스를 추가하여 직접적인 관계를 갖지 않도록 만든다.
/* 인터페이스의 느슨한 결합 예제 */
class A { 
//  public void methodA(B b){ // 강한 결합, A클래스와 B클래스가 직접 관계 있음
    public void methodA(I i){ // 인터페이스 I를 구현한 넘들의 인스턴스만 들어와라 
        i.methodB(); 
    }
}

interface I {
    void methodB();
}

class B implements I{
    public void methodB() {
        System.out.println("methodB in B class");
    }
}

class C implements I{ // C클래스를 추가해도 A클래스는 변경하지 않아도 됨
    public void methodB() {
        System.out.println("methodB in C class");
    }
}

public class interfaceTest {
    public static void main(String[] args) {
        A a = new A();
        a.methodA(new B()); // "methodB in B class"
        a.methodA(new C()); // "methodB in C class"
    }
}

7.9 디폴트 메서드와 static메서드

  • 인터페이스에 디폴트 메서드, static메서드 추가 가능(JDK 1.8~)
  • 인터페이스에 새로운 메서드(추상 메서드)를 추가하기 어려움
    • 인터페이스를 구현하는 클래스들이 새로 추가한 메서드를 전부 완성시켜야 하므로.
    • 해당 문제를 해결하기 위해 디폴트 메서드가 추가됨

디폴트 메서드는 인스턴스 메서드이다(인터페이스 원칙 위반)

interface MyInterface {
	void method();
	default void newMethod() {} // 디폴트 메서드는 몸통이 있음
}

디폴트 메서드가 기존의 메서드와 충돌할 때의 해결책

  1. 여러 인터페이스의 디폴트 메서드 간의 충돌
    • 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩해야 한다.
  2. 디폴트 메서드와 조상 클래스의 메서드간의 충돌
    • 조상 클래스의 메서드가 상속되고, 디폴트 메서드는 무시된다.

사실, 충돌 생기면 그냥 고민하지 말고 직접 오버라이딩해서 해결하는 게 낫다

profile
Every Run, Learn Counts.

0개의 댓글