인터페이스
- 추상메소드의 집합(인터페이스의 핵심 메소드)
- 추상메소드의 집합은 추상클래스도 똑같은거 아닌가? 그럼 인터페이스와의 차이가 무엇일까
추상클래스는 일반 클래스이다. 근데 메소드들 중 추상메소드를 가지고 있으니까 추상클래스가 되는것이다.
인터페이스는 오로지 추상메소드만 가지고있다.(구현된것이 아무것도 없다) - 구현된것이 전혀 없는 설계도, 껍데기(모든 멤버가 public)
interface 인터페이스이름 {
public static final 타입 상수이름 = 값; 오로지 상수만 가질수있다. 변수, iv, lv모두 안된다.
public abstract 메소드이름(매개변수목록); 추상메소드이다.
}
인터페이스의 모든 멤버는 public이다.
interface PlayingCard {
public static final int SPADE = 4;
final int DIAMOND = 3;
static int HEART = 2;
int CLOVER = 1;
전부 상수이다. interface에 있는 모든 것은 public static final이 자동으로 붙는다.
public abstract String getCardNumber();
String getCardKind();
두 개 다 추상메소드이다. interface에 있는 모든 메소드는 public abstract 이다.
}
- 인터페이스의 조상은 인터페이스만 가능하다. (Object 가 최고 조상이 안된다)
- 클래스와는 다르게 다중 상속이 가능하다 ( 추상 메소드는 구현부가 없어서 충돌해도 문제가 없다 그래서 다중 상속이 문제 없다.)
인터페이스의 구현
- 인터페이스도 추상 메소드만으로 구성되어있기때문에 미완성 설계도이다. 구현은 그 추상메소드를 완성하는것이다.
class 클래스이름 implements 인터페이스이름 {
// 인터페이스에 정의된 추상메소드를 모두 구현해야한다.
}
차이점이 보이는가?
클래스를 상속할때는 extends였고
인터페이스를 상속할때는 implements(구현)를 사용한다.
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) { //내용 생략 }
}
이처럼 인터페이스에 추상메소드를 implements를 통해 받아서 추상메소드를 구현시킨다.
abstract class Fighter implements Fightable {
public void move(int x, int y) { // 내용 생략 }
}
이처럼 인터페이스를 통해 구현을 하는데 똑같이 구현을 안한 메소드가 있다면 그 클래스는 abstract클래스가 된다.
인터페이스를 이용한 다형성
- 인터페이스도 구현 클래스의 부모
class Fighter extends Unit implements Fightable {
public void move(int x, int y) { // 내용 생략 }
public void attack(Fightable f) { // 내용 생략 }
}
Unit을 상속받는 Fighter는 Fightable을 구현하고 있다. 이는 다중 상속(충돌)과도 같지만
인터페이스는 구현부가 아예 없다. 그래서 충돌이 안된다.
Unit u = new Fighter();
Fightable f = new Fighter();
조상인 Unit도 Fighter객체를 바라볼수 있고 인터페이스인 Fightable도 Fighter객체를 바라볼수 있는것이다.
- 인터페이스 타입 매개변수는 인터페이스 구현한 클래스의 객체만 가능하다
interface Fightable {
void move(int x, int y);
void attack(Fightable f);
}
attack에 매개변수가 인터페이스인 Fightable만 가능하고 이는 Fightable을 구현한 클래스만 사용 가능하다.
- 인터페이스를 메소드의 리턴타입으로 지정할 수 있다.
Fightable method() {
....
Fighter f = new Fighter();
return f;
}
Fightable과 리턴타입인 Fighter(f)는 다르지만 다형성에 의해 리턴이 가능하다
다만 반환 타입은 꼭 인터페이스인 Fightable을 구현해야한다.
그럼 위에 두 줄을 변환할 수 있다.
return new Fighter();
와 같다는 것이다.
Fightable f = method(); == Fightable f = new Fighter();
이렇게 작성 할 수 있다.
인터페이스의 장점
- 두 대상(객체) 간의 '연결, 대화, 소통'을 돕는 '중간 역할'을 한다.
- 선언(설계)와 구현을 분리시킬 수 있게 한다.
- 인터페이스 덕분에 B가 변경되어도 A는 안바꿀 수 있게 된다.(느슨한 결합)
- A(User) ------▶ B(Provider) 이렇게 직접적으로 연결되어있지 않고 A -----▶ Interface안에 B가 있다. 즉, A는 B를 직접적으로 건들이지 않고 인터페이스만 참조하면서 B가 변경되었다고 굳이 A를 바꿀 필요가 없다
직접적으로 간섭하는 코드를 짜보자
class A {
public void methodA (B b) {
b.method();
}
}
A의 클래스에 있는 methodA는 B를 직접 참조하여 메소드를 만든다.
class B {
public void methodB() {
System.out.println("methodB()");
}
}
class InterfaceTest {
public static void main(String[] args) {
A a = new A();
a.methodA(new B());
}
}
하지만 이것을 간접적으로 만든다면
class A {
public void methodA(I i) {
i.methodB();
}
}
interface I { void methodB(); }
class B implements I {
public void methodB() {
System.out.println("methodB()");
}
}
이렇게 한다면 A는 B를 직접적으로 참조하는것이 아니라 interface를 참조하게 되면서 느슨하게 만들어진다.
그럼 무엇이 장점인가?
class C implements I {
public void methodB() {
System.out.println("methodB() in C");
}
}
이렇게 다른 클래스가 B를 다루고 싶을때 I를 implements 하면서 A의 내용을 변경해주지 않아도 되는것이다.
- 개발 시간을 단축할수 있다( 딱봐도 엄청난 장점입니다 ㅎㅎ)
예를 들어 위에 있는 경우를 들자면 만약 인터페이스가 없다면 A는 B가 완성되지 않았다면 A는 일을 하기 위해 대기 해야한다. 하지만 B가 인터페이스에 감싸져있다면 A는 인터페이스를 참조해 B를 기다리지 않고 일을 진행할 수있다. - 변경에 유리한 유연한 설계가 가능하다.
- 표준화가 가능하다.(JDBC) = 우리는 작업을 할 때 DB를 기준으로 많은 일을 하게 될 것이다.
- 서로 관계없는 클래스들을 관계를 맺어줄 수 있다.
상황을 하나 고려해 봐야한다
Unit이라는 큰 클래스가 있고 그 밑에 GroundUnit과 AirUnit이 있다
Ground는 땅에서 다니는 SCV, Tank, Marine이 있고
Air는 Dropship이 있다.
근데 내가 기계로봇들을 치료해주려고 repair라는 메소드를 하나 만들었다.
그런데 Tank,Marine과 Dropship이 다른 곳에 있다 이때 interface로 해결할 수 있다
interface Repairable { }
class SCV extends GroundUnit implements Repairable {
// ...
}
class Tank extends GroundUnit implements Repairable {
// ...
}
class Dropship extends AirUnit implements Repairable {
// ...
}
이렇게 interface된것을 상속받아 기계만 repair할수 있게 만들수 있는것이다.
이것이 interface의 어마어마한 장점이다.
void repair (Repairable r) { 이때 들어올수 있는 Reapirable을 구현한 클래스만 들어올 수 있다.
if(r instanceof Unit) {
Unit u = (Unit) r;
while(u.hitPoint != u.MAX_HP) {
u.hitPoint++;
}
}
}
이런식으로 사용할 수 있다.
번외 ( static 메소드, default 메소드 )
- 인터페이스에 디폴트 메소드, static메소드 추가 가능( JDK1.8부터 가능 )
- 디폴트 메소드를 추가 할 수 있는 능력이 나온 이유
- 인터페이스에 새로운 메소드(추상 메소드)를 추가하기 어렵다는 문제가 있다.
이게 무슨 말이냐면 인터페이스를 사용하다가 추가적으로 필요한 메소드가 생겼다. 인터페이스에 추가하는 메소드는 물론 추상메소드이고 때문에 문제가 생기는데 해당 인터페이스와 관련이 있는 모든 클래스가 해당 메소드를 구현해야 한다는것이다. - 해결 방법으로 default 메소드가 나왔다 사용 방법은 interface에서 메소드 앞에 default를 붙이고 그대로 구현을 하면 된다.
- 인터페이스에 새로운 메소드(추상 메소드)를 추가하기 어렵다는 문제가 있다.
- 이는 인터페이스 원칙이 위반된다 = 디폴트 메소드가 기존의 메소드와 충돌할 때의 해결책 정리
- 여러 인터페이스의 디폴트 메소드 간의 충돌
- 인터페이스를 구현한 클래스에서 디폴트 메소드를 오버라이딩해줘야 한다.
- 디폴트 메소드와 조상 클래스의 메소드 간의 충돌
- 조상 클래스의 메소드가 더 우선시 되고, 디폴트 메소드는 무시한다.
- 여러 인터페이스의 디폴트 메소드 간의 충돌
'Java > 객체 지향' 카테고리의 다른 글
추상 클래스와 추상 메소드 (0) | 2023.05.11 |
---|---|
제어자와 캡슐화 (0) | 2023.05.09 |
생성자(ft. this, super) (0) | 2023.05.08 |
오버로딩(Overloading) & 오버라이딩(Overriding) (0) | 2023.05.08 |
호출 스택(call stack) (0) | 2023.05.08 |