인터페이스(interface)
인터페이스를 이해하려면 추상화, 다형성에 대한 이해가 필수적이다.
❓인터페이스란
⭐ 추상 메서드의 집합
핵심
구현된 것이 전혀 없는 설계도, 껍데기(모든 멤버가 public)
interface 인터페이스이름 {
public static final 타입 상수이름 = 값;
public abstract 메서드이름(매개변수목록);
}
인터페이스는 변수를 가질 수 없다. 무조건 상수만 가질 수 있다.
메서드는 전부 다 추상메서드 = 추상메서드의 집합
interface PlayingCared {
public static final int SPACE = 4;
final int DIAMOND = 3; //public static final int DIAMOND = 3;
static int HEART = 2; //public static final int HEART = 2;
int CLOVER = 1; //public static final int CLOVER = 1;
public abstract String getCardNumber();
String getCardKind(); // public abstract String getCardKind();
}
`public static final`
`public abstract`
이것이 default다. 없다면 생략된 것이다.
인터페이스의 상속
- 인터페이스의 조상은 인터페이스만 가능(Object가 최고 조상이 아님)
- 다중 상속이 가능하다. → 추상메서드는 충돌해도 문제가 없기 때문에
인터페이스의 구현
- 인터페이스에 정의된 추상 메서드를 완성하는 것
- 구현부는 인터페이스의 추상 메서드를 모두 구현해야 한다.
- 일부만 구현하는 경우, 클래스 앞에 abstract를 붙여야 한다.
abstract class Fighter implements Fightable {
public void move(int x, int y) { /* 생략 */ }
public void attack() { /* 생략 */ }
}
❓추상 클래스와 인터페이스의 공통점
→ 추상 메서드를 가지고 있다.(미완성 설계도)
❓추상 클래스와 인터페이스의 차이점
→ 인터페이스는 변수(iv)를 가질 수 없다.
→ 생성자, 인스턴스메서드도 안됨
인터페이스를 이용한 다형성
- 인터페이스도 구현 클래스의 부모인가? → 그렇게 칠 수 있다. 엄밀히 말하면 부모는 클래스만 가능하다.
- 인터페이스 타입의 인스턴스 생성이 가능
interface Fightable{ /* 생략 */ }
abstract class Unit{ /* 생략 */ }
class Fighter extends Unit implements Fightable {
public void move(int x, int y) { /* 생략 */ }
public void attack() { /* 생략 */ }
}
`Unit u = new Fighter();`
`Fightable f = new Fighter();`
둘 다 가능하다.
대신 `Fighterble`에 정의된 메서드만 사용 가능하다.
- 인터페이스 타입 매개변수는 인터페이스를 구현한 클래스의 객체만 가능
interface Fightable {
void move(int x, int y);
void attack(Fightable f); //**
}
`attack`메서드는 `Fightable`인터페이스를 구현한 클래스의 인스턴스만 들어올 수 있다!
- 인터페이스를 메서드의 리턴타입으로 지정할 수 있다.
Fightable method() {
Fighter f = new Fighter();
return f; //Fighter는 Fightable의 구현클래스기 때문에 인터페이스로 반환가능
}
class Fighter extends Unit implements Fightable {
public void move(int x, int y) { /* 생략 */ }
public void attack() { /* 생략 */ }
}
❓인터페이스로 리턴하는 이유가 뭘까
→ 다형성과 유연성을 활용하기 위해서
인터페이스를 반환하면 다음과 같은 장점이 있다.
- 다형성 활용: 인터페이스를 반환하면 여러 클래스의 인스턴스를 하나의 인터페이스 타입으로 다룰 수 있다.
이는 코드의 유연성을 높여준다.
예를 들어, `Fightable` 인터페이스를 구현하는 다른 클래스도 반환될 수 있으므로 코드를 확장하거나 변경할 때 유리하다.
- 코드 재사용: `Fightable `인터페이스를 구현하는 클래스가 여러 곳에서 사용될 경우,
인터페이스를 반환함으로써 해당 클래스의 인스턴스를 여러 곳에서 재사용할 수 있다.
즉 코드 중복을 피할 수 있다.
- 확장성: 나중에 코드를 확장할 때 새로운 클래스를 만들고 이 클래스가 `Fightable` 인터페이스를 구현하도록 하면,
이 새로운 클래스도 반환될 수 있으므로 코드 변경 없이 확장이 가능하다.
- 추상화: 인터페이스를 반환함으로써 구체적인 구현 세부사항을 숨길 수 있다.
이는 코드를 보다 추상적으로 만들어서 복잡성을 낮출 수 있다.
예를 들어 `Fightable` 인터페이스를 구현하는 다른 유닛들이 필요한 경우
해당 유닛 클래스를 작성하고, 그것을 `Fightable`로 반환하는 메서드에 적용하면 된다.
이렇게 하면 코드의 확장성이 높고 유지 보수가 쉬워진다.
인터페이스의 장점
❓인터페이스란
- 두 대상(객체) 간의 ‘연결, 대화, 소통’을 돕는 ‘중간 역할’을 한다.
- 사람이 기계를 직접 조작하는 것보다는 껍데기를 통해 조작하는 게 더 편하다.
- 선언(설계)과 구현을 분리시킬 수 있게 한다.
- 변경에 유리하고 유연한 코드가 된다.
- 구현부가 변경돼도 선언부는 안 바꿔도 된다.(느슨한 결합)
예시
아래와 같은 코드가 있을 때 `B`가 `C`라는 신규 클래스로 바꿔야 한다면
`A.methodA` 메서드를 변경해줘야 한다.
- 의존관계 = A → B
class A {
public void methodA(B b) {
b.methodB();
}
}
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());
}
}
이렇게 `B`를 인터페이스의 선언부와 구현부로 쪼갠다면 `A`는 건드리지 않고,
`C`라는 클래스만 새로 추가해 `a.methodA(new C());` 이렇게 사용하면 된다.
- 의존관계 = A → I → C
class A {
public void methodA(I i) {
i.methodB();
}
}
interface I { void methodB(); }
class B implements I {
public void methodB() {
System.out.println("methodB()");
}
}
class InterfaceTest {
public static void main(String args[]){
A a = new A();
a.methodA(new B());
}
}
장점
- 개발 시간을 단축할 수 있다.
→ 구현부가 개발이 안되어있어도, 선언부가 구현이 되어있기 때문에 추상메서드지만 구현부가 완성된 것을 고려해 코드를 작성할 수 있다.
- 변경에 유리한 유연한 설계가 가능하다.
→ 위의 코드에서 설명
- 서로 관계없는 클래스들의 관계를 맺어줄 수 있다.
위의 상속 계층도에서 SCV, Tank, Dropship 클래스만 사용가능한 repair() 메서드를 만들고 싶은데, 아래와 같이 GroundUnit타입의 매개변수를 쓰자니 Marine클래스도 repair() 메서드를 사용하게 되어 원치 않은 결과를 얻게 된다.
void Repair(Tank t) {
// Tank를 수리한다
}
void Repair(Dropship d) { // 오버로딩
// Dropship을 수리한다
}
// 과도한 오버로딩을 방지하기위해 조상 클래스 타입 매개변수를 이용
void Repair(GroundUnit gu) {
// 매개변수로 넘겨진 GroundUnit을 수리한다
}
아래처럼 Repairable 인터페이스를 구현했다는 공통점을 가지고 이들에 대해 관계를 맺어줄 수 있다.
interface Repairable {}
class SCV extends GroundUnit implements Repairable { }
class Tank extends GroundUnit implements Repairable { }
class Dropship extends AirUnit implements Repairable { }
// 실제 메서드 구현시
void Repariable(Repairable r) {
if (r instanceof Unit) {
Unit u = (Unit) r;
while (u.hitPoint != u.Max_HP) {
u.hitPoint++;
}
}
} // repair(Repairable r)
`Repariable`메서드에 대해 `Repairable`인터페이스를 구현한 객체만 들어올 수 있게 한다!
디폴트 메서드 static 메서드
- 인터페이스에 디폴트 메서드, static메서드 추가 가능(JDK1.8부터)
인터페이스에는 새로운 메서드(추상 메서드)를 추가하기 어렵다는 특징이 있다.
A 인터페이스에 메서드를 추가하면 A의 구현 클래스들은 추가된 메서드를 구현해줘야 한다.
이 현상의 해결책으로 디폴트 메서드가 나왔다.
before
interface MyInterface {
void method();
void newMethod() {} // 디폴트 메서드는 몸통이 있음
}
after
interface MyInterface {
void method();
default void newMethod() {} // 디폴트 메서드는 몸통이 있음
}
이제 구현부가 인터페이스의 모든 추상 메서드를 구현하지 않아도 된다.
이걸로 괜찮을까?
디폴트 메서드는 인스턴스 메서드다. = 인터페이스 원칙 위반 (예외인 것)
또, 디폴트 메서드가 기존의 메서드와 충돌한다.
→ 구현 클래스에서 디폴트 메서드를 오버라이딩해야 한다.
충돌 생기면 그냥 오버라이딩 하자!
'📝 끄적끄적 > 🖥️ 자바의 정석' 카테고리의 다른 글
[자바의 정석] - 다형성 (0) | 2023.09.25 |
---|---|
[자바의 정석] - 추상 클래스와 추상메서드 (0) | 2023.09.25 |
[자바의 정석] 참조변수의 형변환, instanceof 연산자 (0) | 2023.09.24 |
[자바의 정석] 제어자, 캡슐화 (0) | 2023.09.24 |
[자바의 정석] 상속, 참조변수 super, 생성자 super() (0) | 2023.09.23 |
[자바의 정석] 생성자, 변수/멤버 변수의 초기화 (0) | 2023.09.22 |