인터페이스(interface)
- 추상 메서드의 집합
- 구현된 것이 전혀 없는 설계도. 껍데기(모든 멤버가 public)
- 원래 인퍼테이스의 모든 메서드는 추상메서드이지만 static메서드와 디폴트 메서드(default method)가 추가 되었음
추상클래스와 인터페이스와의 차이
추상클래스: 일반클래스인데 추상 메서드도 같이 가지고 있음
인터페이스: 아무것도 없는데(멤버변수x) 추상 메서드만 가지고 있음.(iv, cv xx, 상수만)
추상클래스 -> 미완성 설계도, 인터페이스 -> 기본 설계도
인터페이스의 작성
interface 인터페이스이름 {
public static final 타입 상수이름 = 값; // 상수
public abstract 메서드이름(매개변수목록); // 추상메서드
}
모든 멤버변수(상수)는 public static final이야하며 이를 생략할 수 있다.
모든 메서드는 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 final int HEART = 2;
int CLOVER = 1; // public static final int CLOVER = 1;
String getCardNumber(); //public abstract String getCardNumber();
}
인터페이스의 상속
- 인터페이스의 조상은 인터페이스만 가능(Object가 최고 조상 아님)
- 다중 상속이 가능(추상메서드는 충돌해도 문제 없음)
(자바가 단일 상속인 이유가 선언부가 다르면 둘다 상속받으면 그만이지만, 선언부가 같고 내용{}이 다르면 어느 쪽을 상속받을 지 결정할 수 없기 때문이다.)
interface Movable {
/* 지정된 위치(x,y)로 이동하는 기능의 메서드 */
void move(int x, int y);
}
interface Attackable {
/* 지정된 대상(u)를 공격하는 기능의 메서드 */
void attack(Unit u);
}
interface Fightable extends Movable, Attackable { }
-> 자손인터페이스(Fightable)은 조상 인터페이스(Movable, Attackable)에 정의된 멤버를 모두 상속받기 때문에 Fightable 자체에는 정의된 멤버가 없지만 Fightable은 상속받은 두개의 추상 메서드(move, attack)을 멤버로 갖게 됨
인터페이스의 구현
- 인터페이스에 정의된 추상 메서드를 완성하는 것(미완성 설계도 완성하기)
- 추상클래스는 extends를 사용해서 상속을 통해 추상메서드를 완성하는 것처럼 인터페이스는 implements를 사용해서 추상메서드를 완성한다.
class 클래스이름 implements 인터페이스이름 {
// 인터페이스에 정의된 추상메서드를 모두 구현한다.
}
class Fighter implements Fightable {
public void move(int x, int y){ }
public void attack(Unit u) {}
}
-> Fighter클래스는 Fightable 인터페이스를 구현한다!(Fightable 인터페이스에 있는 추상메서드의 몸통을 만들어줬다)
abstract class Fighter implements Fightable {
public void move(int x, int y) {}
}
-> 일부만 구현하는 경우, 클래스 앞에 abstract를 붙여야 함(추상 클래스와 동일함)
추상클래스의 구현과 인터페이스 구현
추상클래스의 구현
class AudioPlayer extends Player {
void move(int x, int y){}
void stop() {}
}
-> AudioPlayer 완성된 클래스(설계도) ,Player(미완성된 설계도)
인터페이스의 구현
class Fighter implements Fightable {
public void move(int x, int y){ }
public void attack(Unit u) {}
}
-> Fighter 완성된 인터페이스(설계도), Fightable(미완성된 설계도)
추상클래스와 인터페이스의 공통점과 차이점
- 공통점: 추상메서드(미완성 설계도)를 가지고 있다.
- 차이점: 추상클래스는 일반클래스 + 추상메서드 / 인터페이스는 추상메서드만!(iv를 가질 수 없음)
추상클래스 = iv + 생성자 + im + 추상메서드 | 인터페이스 = 추상메서드 + 상수 + (static 메서드 + default 메서드) |
인터페이스를 이용한 다형성
(1) 인터페이스도 구현 클래스의 부모이다.
class Fighter extends Unit implements Fightable {
public void move(int x, int y){ }
public void attack(Unit u) {}
}
-> 상속과 구현을 동시에 할 수 있음!
Unit u = new Fighter(); // 조상클래스
Fightable f = new Fighter(); // 인터페이스
-> Fighter 클래스로 Fightable 인터페이스를 구현했으니까 Fightable 참조변수로 Fighter클래스를 참조 하는게 가능함(다형성)
(2) 인터페이스 타입 매개변수는 인터페이스를 구현한 클래스의 객체만!!
- 인터페이스 Fightable을 클래스 Fighter가 구현했을 때, Fighter인스턴스를 Fifghtable의 참조변수로 참조하는 것이 가능하다.
interface Fightable {
void move(int x, int y);
void attack(Fightable f); // Fightable의 인퍼테이스를 구현한 클래스의 인스턴스만 가능!
}
class Fighter implements Fightable {
public void move(int x, int y){}
public void attack(Unit u) {}
}
Fightable f = (Fightable)new Fighter(); // Fightable f = new Fighter();
인퍼테이스는 다음과 같이 메서드의 매개변수 타입으로 사용될 수 있다.
void attack(Fightable f) {
...
}
f.attack(new Fighter());와 같이 할 수 있음!
-> attack 메서드를 호출할 때는 매개변수로 Fightable 인터페이스를 구현한 클래스의 인스턴스를 넘겨주어야 한다.!
(3) 인터페이스를 메서드의 리턴타입으로 지정할 수 있다.
Fightable method() {
...
Fighter f = new Fighter(); // 이 두 문장을 한 문장으로 바꾸면 return new Fighter();
return f;
}
-> 리턴타입이 인터페이스이면? 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 의미
-> Fightable은 리턴타입(반환타입) -> Fightable 인터페이스를 구현한 클래스의 인스턴스를 반환(return new Fighter();)
-> 메서드의 반환타입이 Fightable(인터페이스)이라면 안에 매개변수도 Fightable이 되어야 함 (메서드의 기본형처럼)
-> Fightable method의 문장을 한 문장으로 바꾸면 return new Fighter( );로 할 수 있음
public class FighterTest {
public static void main(String[] args) {
Unit2 u = new Fighter();
Fable f = new Fighter(); //Fighter f = new Fighter();도 가능
u.move(100,200);
u.stop();
// u.attack(); Unit에는 attack( )이 없어서 호출불가
f.move(100, 200);
Fighter f2 = new Fighter(); // 한줄로 줄이면 f.attack(new Fighter());
f.attack(f2);
// f.stop(); Fightable에는 stop()이 없어서 호출불가
}
}
abstract class Unit2 { // Unit2 추상클래스 생성
int x, y;
abstract void move(int x, int y); // 정수 값만큼 이동하는 메서드
void stop() {System.out.println("멈춥니다.");}
}
interface Fable { // Fable 인터페이스 생성
void move(int x, int y); // public abstract 생략
public abstract void attack(Fable f);
}
class Fighter extends Unit2 implements Fable { // Unit2와 Fable을 상속 구현해 메서드를 완전하게 만듦)
// 오버라이딩 규칙: 조상보다 접근제어자의 범위가 좁으면 안된다.( Fable의 범위는 public, default -> public )
public void move(int x, int y) {
System.out.println("["+x+","+y+"]로 이동");
}
public void attack(Fable f) {
System.out.println(f + "를 공격");
}
}
인터페이스의 장점
1) 두 대상(객체) 간의 ‘연결, 대화, 소통’을 돕는 ‘중간 역할’을 한다.
예) GUI(Graphic User Interface, 윈도우)
2) 선언(설계)[껍데기]와 구현[알맹이]을 분리시킬 수 있게 한다. (변경이 유리함)
// 알맹이
class B {
public void method() {
System.out.println("methodInB");
}
}
// 껍데기
interface I { // classB에서 껍데기 부분(선언부)만 빼내서 새로운 인터페이스 생성
public void method();
}
// 껍데기와 알맹이 분리
class B implements I {
public void method() {
System.out.println("methodInB");
}
}
-> 껍데기 + 알맹이 -> 유연하지 않고 변경에 불리
-> 인터페이스를 새로 구현해서 껍데기와 알맹이를 분리시켰다.
-> A가 B를 사용(A가 B에 의존) 하지만 만약 A가 C를 사용하고 싶다면? 코드를 전체를 바꿔야 할 것이다. 하지만 인터페이스를 사용한다면?
-> 인터페이스 덕분에 B가 변경되어도 A는 안 바꿀 수 있게 된다.(느슨한 결합)
-> A클래스는 B와 직접적인 관계가 없고 인터페이스와 관련 있다. (알맹이만 바꾸면 되고 껍데기는 똑같이!)
직접적인 관계의 두 클래스(A-B)
-> 직접적인 관계의 두 클래스는 한 쪽(Provider)이 변경되면 다른 한 쪽(User)도 변경되어야 한다.
간접적인 관계의 두 클래스(A-I-B)
-> B클래스의 method를 추상메서드로 갖는 인터페이스 작성
class A {
public void methodA(I i) {
i.methodB();
}
}
class C implements I { //C 클래스로 I 인터페이스를 구현
public void methodB() {
System.out.println("methodB() in C");
}
}
-> A클래스도 B클래스의 메서드를 사용하는 것이 아니라 인터페이스 I를 사용!(A는 더 이상 B와 관계 X)
-> B를 C클래스로 변경해도 A클래스는 변경하지 않아도 된다! 새로 만들 수 있다!
public class InterfaceTest {
public static void main(String[] args) {
A a = new A();
B a1 = new B(); // 한 줄로 줄이면 a.method(new B());
a.method(a1); // A가 B를 사용(의존)
a.method(new C());
}
}
// B클래스의 선언과 구현을 분리. method의 구현
class B implements I {
public void method() {
System.out.println("B클래스의 메서드");
}
}
// method의 선언
interface I {
void method() ;
}
class A {
public void method(I i) { // 인터페이스 I를 구현한 클래스만 들어와라
i.method();
}
}
// C클래스 추가
class C implements I {
public void method() {
System.out.println("C클래스의 메서드");
}
}
3) 개발시간을 단축할 수 있다.
- 메서드를 호출하는 쪽에서는 선언부만 알면 되기 때문에 일단 인터페이스가 작성되면, 이를 사용해서 프로그램을 작성하는 것이 가능하다.
- 동시에 인터페이스를 구현하는 클래스를 작성하게 되면, 양쪽에서 동시에 개발을 진행할 수 있다.
- A가 B를 완성하려면 B를 완성할 때까지 A는 기다려야 하지만 인터페이스 I가 있다면 추상메서드는 호출 가능하기 때문에 A클래스는 I를 이용해서 코드를 먼저 작성할 수 있다. 그리고 B클래스는 구현하기만 하면 된다.
4) 변경에 유리한 유연한 설계가 가능하다.
- A -> I -> B or C ㅇ(앞의 예제처럼)
5) 표준화가 가능하다.
- 프로젝트에 사용되는 기본 틀을 인터페이스로 작성한 다음. 개발자들에게 인터페이스를 구현해 프로그램을 작성하도록 하게 해서 보다 일관되고 정형화된 프로그램의 개발이 가능하다. (JDBC: 인터페이스 집합)
6) 서로 관계없는 클래스들에게 관계를 맺어줄 수 있다.
- 서로 아무런 관계 없는 클래스들에게 하나의 인터페이스를 공통적으로 구현하도록 함으로써 관계를 맺어줄 수 있다.
Unit클래스, GroundUnit 지상 유닛, AriUnit 공중 유닛,
인간: Marine 병사, 로봇:SCV, Tank 탱크, Dropship 수송선(비행기)
예제) 기계 수리 메서드 작성!(SCV, Tank, Dropship)
interface Repairable() {}
class SCV extends GroundUnit implements Repairable() { }
class Tank extends GroundUnit implements Repairable() { }
class Drophsip extends AirUnit implements Repariable() { }
void repair(Repairable r) { //Repairable 인터페이스를 구현한 클래스만!
if(r instanceof Unit) { }
}
Repairable 인터페이스로 묶어주게 된다면 Repairable을 구현했다는 공통점이 생겨서 Repairable의 자손이 된 것!
-> 상속계층도에서 공통점을 찾기 어려울 때는 인터페이스를 구현하게 바꿔준다.
-> Repariable 인터페이스를 구현한 클래스: SCV, Tank, Dropship
디폴트 메서드와 static 메서드
- 원래 인터페이스에 추상 메서드만 선언할 수 있었는데, JDK1.8부터 디폴트 메서드와 static메서드도 추가 가능
디폴트 메서드
- 디폴트 메서드는 추상 메서드의 기본적인 구현을 제공하는 메서드로, 추상 메서드가 아니어서 디폴트 메서드가 새로 추가되어도 해당 인터페이스를 구현한 클래스를 변경하지 않아도 된다.
- 인터페이스에 새로운 메서드(추상 메서드)를 추가하기 어려웠기 때문에 디폴트 메서드가 생겨난 것!!
- 앞에 키워드 default를 붙이며, 추상 메서드와 달리 일반 메서드처럼 몸통 { }이 있어야 한다. 접근제어자는 public이며, 생략 가능하다.
interface MyInterface {
void method();
void newMethod(); // 추상메서드
}
위의 코드를 아래 코드로 변경!
interface MyInterface {
void method();
default void newMethod() {} // 디폴트 메서드
}
-> 조상 클래스에 새로운 메서드를 추가한 것과 동일해짐
1) 여러 인터페이스의 디폴트 메서드 간의 충돌: 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩 해야 한다.
2) 디폴트 메서드와 조상 클래스의 메서드 간의 충돌: 조상 클래스의 메서드가 상속되고, 디폴트 메서드는 무시
-> 그냥 직접 오버라이딩 하면 해결됨
예제) Parent 클래스에 method2 / MyInterface 클래스에 method1, method2, staticMethod / MyInterface 클래스에 method1, staticMethod / Parent 클래스를 상속받고 MyInterface, MyInterface2를 구현하는 Child 클래스 생성하고 Child 클래스에 method1 추가!
class Child extends Parent implements MyInterface, MyInterface2 {
public void method1() {
System.out.println("method1() in Child"); // 오버라이딩
}
}
class Parent {
public void method2() {
System.out.println("mthod2() 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("staticMehtod() in MyInterface2");
}
}
public class interfaceTest2 {
public static void main(String[] args) {
Child c = new Child();
c.method1(); //method1() in Child 인터페이스의 메서드가 아닌 child클래스의 메서드
c.method2(); //method2() in Parent 안터페이스의 메서드가 아닌 parent클래스의 메서드
MyInterface.staticMethod(); // staticMethod() in MyInterface
MyInterface2.staticMethod(); // staticMethod() in MyInterface2
}
}
'멀티캠퍼스 풀스택 과정 > Java의 정석' 카테고리의 다른 글
자바의 정석5-1 예외처리(exception handling) (0) | 2022.01.06 |
---|---|
자바의 정석4-3 내부클래스와 익명클래스 (0) | 2022.01.06 |
자바의 정석4-1 추상 클래스(abstract class)와 추상메서드 (0) | 2022.01.05 |
자바의 정석3-6 instanceof 연산자, 다형성의 장점(1,2) (0) | 2022.01.05 |
자바의 정석3-5 캡슐화(encapsulation)와 다형성(polymorphism) (3) | 2022.01.05 |