인터페이스 (Interface)

박주현·2022년 10월 20일
0

혼공

목록 보기
8/20

인터페이스란?

  • 인터페이스는 일종의 추상클래스이다. 인터페이스는 추상클래스처럼 추상메서드를 갖지만 추상클래스보다 추상화 정도가 높아서 추상클래스와 달리 몸통을 갖춘 일반 메서드 또는 멤버변수를 구성원으로 가질 수 없다. 오직 추상메서드와 상수만을 멤버로 가질 수 있으며, 그 외의 다른 어떠한 요소도 허용하지 않는다.
    추상클래스를 부분적으로만 완성된 '미완성 설계도'라고 한다면, 인터페이스는 구현된 것은 아무 것도 없고 밑그림만 그려져 있는 '기본 설계도'라 할 수 있다.
    인터페이스도 추상클래스처럼 완성되지 않은 불완전한 것이기 때문에 그 자체만으로 사용되기 보다는 다른 클래스를 작성하는데 도움 줄 목적으로 작성된다.

인터페이스의 작성

인터페이스를 작성하는 것은 클래스를 작성하는 것과 같다. 다만 키워드로 class 대신 interface를 사용한다는 것만 다르다. 그리고 interface에도 클래스와 같이 접근제어자로 public 또는 default를 사용할 수 있다.

interface 인터페이스이름 {
	public statice final 타입 상수이름 = 값;
    public abstract 메서드이름(매개변수목록);
}

일반적인 클래스의 멤버들과 달리 인터페이스의 멤버들은 다음과 같은 제약사항이 있다.

- 모든 멤버변수는 public static final 이어야 하며, 이를 생략할 수 있다.
- 모든 메서드는 public abstract 이어야 하며, 이를 생략할 수 있다.
  단, static메서드와 default 메서드는 예외 (JDK1.8 부터)

인터페이스에 정의된 모든 멤버에 예외없이 적용되는 사항이기 때문에 제어자를 생략할 수 있는 것이며, 편의상 생략하는 경우가 많다. 생략된 제어자는 컴파일 시에 컴파일러가 자동적으로 추가해준다.


인터페이스의 장점

  • 개발시간을 단축시킬 수 있다.
    일단 인터페이스가 작성되면 이른 사용해서 프로그램을 작성하는 것이 가능하다.
    메서드를 호출하는 쪽에서는 메서드의 내용에 관계없이 선언부만 알면 되기 때문이다. 그리고 동시에 다른 한 쪽에서는 인터페이스를 구현하는 클래스를 작성하게 하면, 인터페이스를 구현하는 클래스가 작성될 때까지 기다리지 않고도 양쪽에서 동시에 개발을 진핼할 수 있다.

  • 표준화가 가능하다.
    프로젝트에 사용되는 기본 틀을 인터페이스로 작성한 다음, 개발자들에게 인터페이스를 구현하여 프로그램을 작성하도록 함으로써 보다 일관되고 정형화된 프로그램의 개발이 가능하다.

  • 서로 관계없는 클래스들에게 관계를 맺어 줄 수 있다.
    서로 상속관계에 있지도 않고, 같은 조상클래스를 가지고 있지 않은 서로 아무런 관계도 없는 클래스들에게 하나의 인터페이스를 공통적으로 구현하도록 함으로써 관계를 맺어 줄 수 있다.

  • 독립적인 프로그래밍이 가능하다.
    인터페이스를 이용하면 클래스의 선언과 구현을 분리시킬 수 있기 때문에 실제구현에 독립적인 프로그램을 작성하는 것이 가능하다. 클래스와 클래스간의 직접적인 관계를 인터페이스를 이용해서 간접적인 관계로 변경하면, 한 클래스의 변경이 관련된 다른 클래스에 영향을 미치지 않는 독립적인 프로그래밍이 가능하다.

public class Java_bible {
	public static void main(String[] args) {
		Tank tank = new Tank();
		Dropship dropship = new Dropship();

		Marine marine = new Marine();
		SCV scv = new SCV();

		tank.hitPoint = 20;	// Test
		scv.repair(tank);
		System.out.println(tank.hitPoint);	// Test
		scv.repair(dropship);
//		scv.repair(marine);	// error! repair(Repairable) in SCV cannot 
							// be applied to (Marine)

	}
}

interface Repairable {
}

class Unit {
	int hitPoint;
	final int MAX_HP;

	Unit(int hp) {
		MAX_HP = hp;
	}
}

class GroundUnint extends Unit {
	public GroundUnint(int hp) {
		super(hp);
	}
}

class AirUnit extends Unit {
	AirUnit(int hp) {
		super(hp);
	}
}

class Tank extends GroundUnint implements Repairable {
	public Tank() {
		super(150); // Tank의 HP는 150dlek
		hitPoint = MAX_HP;
	}

	@Override
	public String toString() {
		return "Tank";
	}
}

class Dropship extends AirUnit implements Repairable {
	public Dropship() {
		super(125);
		hitPoint = MAX_HP;
	}

	@Override
	public String toString() {
		// TODO Auto-generated method stub
		return "Dropship";
	}
}

class Marine extends GroundUnint {
	public Marine() {
		super(40);
		hitPoint = MAX_HP;
	}

	@Override
	public String toString() {
		// TODO Auto-generated method stub
		return "Marine";
	}
}

class SCV extends GroundUnint implements Repairable {
	public SCV() {
		super(50);
		hitPoint = MAX_HP;
	}

	void repair(Repairable r) {
		if (r instanceof Repairable) {
			Unit u = (Unit) r;
			while (u.hitPoint != u.MAX_HP) {
				/* Unit의 HP를 증가시킨다. */
				u.hitPoint++;

			}
			System.out.println(u + "의 수리가 끝났습니다.");

		}

	}

	@Override
	public String toString() {
		// TODO Auto-generated method stub
		return "SCV";
	}
}

인터페이스의 이해

인터페이스란 도대체 무엇인가? 라는 이해를 돕기 위해서는 다음의 두 가지 사항을 반드시 염두에 두고 있어야 한다.

  • 클래스를 사용하는 쪽(User)과 클래스를 제공하는 쪽(Provider)이 있다.
  • 메서드를 사용(호출)하는 쪽(User)에서는 사용하려는 메서드(Provider)의 선언부만 알면 된다.(내용은 몰라도 된다.
// 
public class Java_bible {
	public static void main(String[] args) {
		A a = new A();
		a.methodA(new B());

	}
}

class A {
	public void methodA(B b) {
		b.methodB();
	}
}

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

위와 같이 클래스 A와 클래스 B가 있다고 하자. 클래스 A(User)는 클래스 B( Provider)의 인스턴스를 생성하고 메서드를 호출한다. 이 두 클래스는 직접적인 관계가 있다.
이 경우 클래스 A를 작성하려면 클래스 B가 이미 작성되어 있어야 한다. 그리고 클래스 B의 methodB()의 선언부가 변경되면, 이를 사용하는 클래스 A도 변경되어야 한다.
이와 같이 직접적인 관계의 두 클래스는 한 쪽(Provider)이 변경되면 다른 한 쪽(User)도 변경되어야 한다는 단점이 있다.

그러나 아래와 같이 클래스 A가 클래스 B를 직접 호출하지 않고 인터페이스를 매개체로 해서 클래스 A가 인터페이스를 통해서 클래스 B의 메서드에 접근하도록 하면, 클래스 B에 변경 사항이 생기거나 클래스 B와 같은 기능의 다른 클래스로 대체 되어도 클래스 A는 전혀 영향을 받지 않도록 하는 것이 가능하다.

public class Java_bible {
	public static void main(String[] args) {
		A a = new A();
		a.methodA(new B());

	}
}

interface I {
	public abstract void methodB();
}

class A {
	public void methodA(I i) {
		i.methodB();
	}
}

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

클래스 A를 작성하는데 있어서 클래스 B가 사용되지 않았다는 점에 주목해야 한다. 기존 'A-B'의 직접적인 관계에서 'A-I-B'의 간접적인 관계로 바뀐 것이다.

인터페이스 I는 실제구현 내용(클래스 B)을 감싸고 있는 껍데기이며, 클래스 A는 껍데기 안에 어떤 알맹이(클래스)가 들어 있는지 몰라도 된다.

interface I {
	public abstract void play();
}

class B implements I {
	@Override
	public void play() {
		System.out.println("play in B class");

	}
}

class C implements I {
	@Override
	public void play() {
		System.out.println("play in C class");

	}
}

class A {
	void autoPlay(I i) {
		i.play();
	}
}

public class Java_bible {
	public static void main(String[] args) {
		A a = new A();
		a.autoPlay(new B());	// void autoPlay(I i) 호출
		a.autoPlay(new C());	// void autoPlay(I i) 호출
	}
}

클래스 A를 작성하는데 클래스 B나 C가 관련되지 않았다는 사실에 주목해야 한다.

클래스 A가 인터페이스 I를 사용해서 작성되긴 하였지만, 이처럼 매겨변수를 통해서 인터페이스 I를 구현한 클래스의 인스턴스를 동적으로 제공받아야 한다.
클래스 Thread의 생성자인 Thread(Runnable target)이 이런 방식으로 되어 있다.

이처럼 매개변수를 통해 동적으로 제공받을 수 도 있지만 다음과 같이 제3의 클래스를 통해서 제공받을 수도 있다. JDBC의 DriverManager클래스가 이런 방식으로 되어 있다.


default 메서드와 static 메서드

원래는 인터페이스에 추상 메서드만 선언할 수 있는데, JDK1.8부터 디폴트 메서드와 static메서드도 추가할 수 있게 되었다.

default 메서드

조상 클래스에 새로운 메서드를 추가하는 것은 별 일이 아니지만, 인터페이스의 경우에는 보통 큰 일이 아니다. 인터페이스에 메서드를 추가한다는 것은, 추상 메서드를 추가한다는 것이고, 이 인터페이스를 구현한 기존의 모든 클래스들이 새로 추가된 메서드를 구현 해야하기 때문이다.

디폴트 메서드는 앞에 키워드 default 를 붙이며, 추상 메서드와 달리 일반 메서드 처럼 몸통{}이 있어야 한다. 디폴트 메서드 역시 접근 제어자가 public이며, 생략가능하다.

interface MyInterface {
	void method();
    void newMethod();	// 추상 메서드
}

interface MyyInterface {
	void method();
    default void newMethod();
}

위의 코드 중 위와 같이 newMethod()라는 추상 메서드를 추가하는 대신 아래와 같이 디폴트 메서드를 추가하면, 기존의 MyInterface를 구현한 클래스를 변경하지 않아도 된다. 즉, 조상 클래스에 새로운 메서드를 추가한 것과 동일해 지는 것이다.
대신, 새로 추가된 디폴트 메서드가 기존의 메서드와 이름이 중복되어 충돌하는 경우가 발생한다. 이 충돌을 해결하는 규칙은 다음과 같다.

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

위의 규칙이 외우기 귀찮으면, 그냥 필요한 쪽의 메서드와 같은 내용으로 오버라이딩 해버리면 그만이라고 함. 그렇다..

예제 코드

package org.tutorials.java.days.tutorails;

public class Java_bible {
	public static void main(String[] args) {
		Child c = new Child();
		c.method1();
		c.method2();
		MyInterface.staticMethod();
		MyInterface2.staticMethod();

	}
}

class Child extends Parent implements MyInterface, MyInterface2 {
	@Override
	public void method1() {
		System.out.println("method1() in Child"); // 오버라이딩
	}

	@Override
	public void method2() {
		// TODO Auto-generated method stub
		super.method2();
	}

}

class Parent {
	public void method2() {
		System.out.println("method2() in Parent");
	}
}

interface MyInterface {
	default void method1() {
		System.out.println("method1() in MyInterface");
	}

	default void method2() {
		System.out.println("method2() in MyInterface");
	}

	static void staticMethod() {
		System.out.println("staticMethod() in MyInterface");
	}

}

interface MyInterface2 {
	default void method1() {
		System.out.println("method1() in MyInterface2");
	}

	static void staticMethod() {
		System.out.println("staticMethod() in MyInterface2");
	}
}

0개의 댓글