(JAVA) 인터페이스

InAnarchy·2022년 10월 24일
0

JAVA

목록 보기
17/18
post-thumbnail

Table of Contents

  • 인터페이스
  • 추상 클래스와 인터페이스의 공통점/차이점
  • 인터페이스의 상속
  • 인터페이스의 구현
  • 다중 인터페이스의 구현
  • 인터페이스를 이용한 다형성
  • 인터페이스의 장점
  • 디폴트 메서드와 static 메서드

인터페이스

  • 추상 메서드의 집합, 일종의 추상 클래스
  • 추상 클래스는 추상 메소드, 생성자, 필드, 일반 메소드도 포함되어있지만
    인터페이스는 추상 메소드와 상수만 포함할 수 있음. (단 JDK1.8부터 static, 디폴트 메서드도 가능)
  • 모든 멤버가 public
  • 인터페이스의 멤버 변수는 public static final이어야하며, 생략 가능하다.
  • 인터페이스의 메서드는 public abstract이어야하며, 생략 가능하다.
  • 인터페이스는 객체를 생성할 수 없다. 그러나 인터페이스 타입의 참조 변수는 선언 가능하다.
interface 인터페이스이름{
    public static final 타입 상수이름 = 값;
    public abstract 메서드이름(매개변수목록);
}
interface PlayingCard {
    public static final int SPADE = 4;
    final int DIAMOND = 3; //public static 생략
    static int HEART = 2; //public생략
    int CLOVER = 1; //public static final 생략

    public abstract String getCardNumber();

    String getCardKind(); //public abstract 생략
}

추상 클래스와 인터페이스의 공통점/차이점

공통점

  • 추상 메서드를 가지고 있다.

차이점

  • 인터페이스는 인스턴스 변수나 생성자를 가질 수 없다.
  • 추상 클래스는 추상 메서드를 가지고 있는 것을 제외하고는 일반적인 클래스와 같다.(생성자, 인스턴스 변수, 인스턴스 메서드 등을 가질 수 있다)
    예시

추상클래스

abstract class Player{ //추상클래스
    boolean pause;
    int currentPos;
    
    Player(){
        pause = false;
        currentPos = 0;
    }
    
    abstract void play(int pos); //추상메서드
    abstract void stop();
    
    void play(){ //인스턴스 메서드
        play(currentPos);
    }
}

인터페이스

interface Fightable{
    void move(int x, int y);
    void attack(Unit u);
} //추상메서드밖에 없음..

인터페이스 구현 예제

interface PhoneInterface{
    final int TIMEOUT = 10000; //상수필드
    void sendCall(); //추상메서드
    void receiveCall(); //추상메서드
    default void printLogo(){ //default메서드
        System.out.println("**phone**");
    }
}

class SamsungPhone implements PhoneInterface {
    //PhoneInterface의 모든 추상메서드 구현

    public void sendCall() {
        System.out.println("따르릉");
    }

    public void receiveCall() {
        System.out.println("전화왔습니다");
    }

    //메서드 추가 착성
    public void flash() {
        System.out.println("전화기에 불이 켜졌습니다");
    }
}

public class InterfaceEx {
    public static void main(String[] args) {
        SamsungPhone sp = new SamsungPhone();
        sp.printLogo();
        sp.sendCall();
        sp.receiveCall();
        sp.flash();
    }
}

인터페이스의 상속

  • 인터페이스의 조상은 인터페이스만 가능
  • Object가 최고 조상 아님
  • 다중상속 가능
  • 인터페이스를 상속받아 클래스를 작성하면 인터페이스의 모든 추상 메서드를 구현해야 한다.
interface Fightable extends Movable,Attackable{}
interface  Movable{
    void move(int x, int y);
}
interface Attackable{
    void attack(Unit u);
}

인터페이스의 구현

  • 인터페이스에 정의된 추상 메서드를 완성하는 것
class 클래스이름 implements 인터페이스{
//추상메서드 구현
}
class Fighter implements Fightable{ //fighter 클래스는 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) {
        //내용 생략
    }
}

또 다음과 같이 상속과 구현을 동시에 할 수도 있다.

class Fighter extends Unit implements Fightable{ 
    public void move(int x, int y) {
    public void attack(Unit u){
    }
}

다중 인터페이스의 구현

  • 하나 이상의 인터페이스를 구현할 수 있다.
  • 각 인터페이스에 선언된 모든 추상 메서드를 구현해야한다.
interface PhoneInterface{
    final int TIMEOUT = 10000; //상수필드
    void sendCall(); //추상메서드
    void receiveCall(); //추상메서드
    default void printLogo(){ //default메서드
        System.out.println("**phone**");
    }
}

interface MobilePhoneInterface extends PhoneInterface{
//인터페이스 상속
    void sendSMS();
    void receiveSMS();
}
interface MP3Interface{
    public void play();
    public void stop();
}
class PDA{
    public int calculate(int x, int y){
        return x + y;
    }
}

//smartPhone은 PDA를 상속받고 두 추상메서드를 구현한다
class SmartPhone extends PDA implements MobilePhoneInterface, MP3Interface {
    public void sendCall() {
        System.out.println("따르르릉");
    }

    public void receiveCall() {
        System.out.println("전화왔엉");
    }

    public void sendSMS() {
        System.out.println("문자 간다");
    }

    public void receiveSMS() {
        System.out.println("문자 왔다");
    }

    public void play() {
        System.out.println("음악 나온다");
    }

    public void stop() {
        System.out.println("음악 끈다");
    }
	
    //추가로 작성한 메서드
    public void schedule() {
        System.out.println("일정 관리");
    }
}

public class InterfaceEx{
    public static void main(String[] args) {
        SmartPhone sp = new SmartPhone();
        sp.printLogo();
        sp.sendCall();
        sp.play();
        System.out.println("3 + 5 = " + sp.calculate(3,5));
        sp.schedule();
    }
}

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

  • 인터페이스(조상) 타입의 참조변수로 구현한 클래스(자식)의 인스턴스를 참조할 수 있으며, 인터페이스 타입으로의 형변환이 가능하다.

  • 이전 글의 매개변수의 다형성과 비슷하다. 매개변수의 다형성

인터페이스 Fightable을 클래스 Fighter가 구현했을 때
다음과 같이 Fighter 인스턴스를 Fighterable타입의 참조변수로 참조하는 것이 가능하다.
(그러나 FIghtable 인터페이스에 선언된 추상 메서드만 쓸 수 있다.)

Fightable f = (Fightable)new Fighter(); 
또는
Fightable f = new Fighter();
//Fightable타입의 참조변수로 Fighter클래스 참조 

따라서 인터페이스는 다음과 같이 메서드의 매개변수 타입으로도 사용될 수 있다.

void attack(Fightable f){
}

인터페이스 타입의 매개변수가 갖는 의미는 메서드 호출 시 해당 인터페이스를 구현한 클래스의 인스턴스를 매개변수로 제공해야 한다는 것이다.

class Fighter extends Unit implements Fightable{
//Fighter클래스가 Unit을 상속받고 Fightable을 구현한다

	public void move(int x, int y){ //내용 생략
    }
    public void attack(Fightable f){ //내용 생략
    }
}

이와 같이 Fighterble 인터페이스를 구현한 Fighter 클래스가 있을 때, attack의 매개변수로 Fighter 인스턴스를 넘겨줄 수 있다. 즉 attack(new Fighter())와 같이 할 수 있다.

또 메서드의 리턴타입으로 인터페이스를 지정할 수 있다.
리턴타입이 인터페이스라는 것은 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 뜻이다.

Fightable method(){
	Fighter f = new Fighter();
    return f; 
    //한 문장으로 하면 return new Fighter();

즉 이 경우, 메서드의 리턴문에서 Fighteralbe인터페이스를 구현한 Fighter 클래스의 인스턴스 주소를 반환한다.

인터페이스의 장점

- 선언(설계)와 구현을 분리시킬 수 있다.

예를 들어 껍데기와 알맹이 모두 가지고 있는 class B를 인터페이스를 이용해 분리시켜보자.

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

우선 method()를 메서드로 갖는 인터페이스를 선언한다. 이는 껍데기다.

interface I{
	public void method(); //추상 메서드, 선언부만 가진 껍데기
}

그리고 class B가 interface I를 구현시킨다.

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

껍데기와 알맹이를 모두 가지고 있는 기존의 class B는 유연성이 떨어지고 변경에 불리하다. (높은 결합도)
그러나 인터페이스를 사용하면 낮은 결합도를 가진 유연한 노드가 된다.

코드를 통해 더 자세히 보도록 하자.

직접적인 관계를 가진 A,B 두 클래스가 있다.
이 코드는 B를 C로 변경하면 A도 함께 변경해야한다.

그러면 간접적인 관계는 어떤 걸까?

B클래스의 껍데기와 알맹이를 분리했다.
먼저 B클래스의 method()를 추상 메서드로 갖는 인터페이스를 작성했다.
A클래스 역시 인터페이스 I를 사용하도록 변경했다. 이 경우 B와는 관계가 없어지므로 B가 바뀌어도 A클래스에는 변경이 없다.

class A{
    public void method(B b){
    //public void method(C b) A가 B 대신 C를 사용하려면 여기도 수정해야함
        b.method(); //b의 메서드를 호출하는 메서드
    }
}

class B{
    public void method(){
        System.out.println("class B's method");
    }
}

//class C{
//    public void method(){
//        System.out.println("class C's method");
//    }
//}

public class Interface {
    public static void main(String[] args) {
        A a = new A();
        a.method(new B()); //A가 B를 사용,의존
        //a.method(new C()) //A가 C를 사용하려면 여기도 수정해야함
    }
}

이 코드에서는 클래스를 추가하려면 따로 추가해야한다. 그렇다면 인터페이스를 이용해보겠다.

class A{
    public void method(I i){ //인터페이스 I를 구현한 것만 들어갈 수 있음
        i.method();
    }
}

//class b의 선언과 구현 분리
interface I{
    public void method(); //method의 선언
}

class B implements I{
    public void method(){ //method의 구현
        System.out.println("class B's method");
    }
}


class C{
    public void method(){
        System.out.println("class C's method");
    }
}

public class Interface {
    public static void main(String[] args) {
        A a = new A();
        a.method(new B()); //A가 B를 사용,의존
    }
}

이 때 C를 추가하면? 인터페이스를 구현하면 된다. 이 경우 클래스 A에는 변화가 없어도 된다.

class A{
    public void method(I i){ //인터페이스 I를 구현한 것만 들어갈 수 있음
        //public void method(C c)
        i.method();
    }
}

//class b의 선언과 구현 분리
interface I{
    public void method(); //method의 선언
}

class B implements I{
    public void method(){ //method의 구현
        System.out.println("class B's method");
    }
}


class C implements I{
    public void method(){
        System.out.println("class C's method");
    }
}

public class Interface {
    public static void main(String[] args) {
        A a = new A();
        a.method(new C());
    }
}

- 서로 관계없는 클래스들을 관계 맺어줄 수 있다.


Unit을 상속받는 클래스들이 있다고 하자.
이 때 Tank와 Dropship을 수리하는 메서드를 만들 때에는
1. 오버로딩을 통해 메서드를 여러개 만들거나
2. 다형성을 이용할 수 있다. 그런데 이 경우에는 해당 클래스와 그 자손만 가능하다.

이 경우 3개의 클래스가 인터페이스를 만들어서 구현한다면

repair 메서드 하나만 추가하면 된다. Repairable을 구현한 클래스의 객체(SCV, Tank, Dropship)들만 이용할 수 있다.

디폴트 메서드와 static 메서드

  • 디폴트 메서드는 추상 메서드의 기본적인 구현을 제공하는 메서드로, 추상 메서드가 아니기 때문에 디폴트 메서드가 새로 추가되어도 해당 인터페이스를 구현한 클래스를 변경하지 않아도 된다.
  • 디폴트 메서드는 앞에 default를 붙이며, 추상 메서드와 달리 일반 메서드처럼 {} 이 있어야한다.
  • 디폴트 메서드의 접근 제어자는 public이며 생략 가능하다.
interface MyInterface{
	void method();
    void newMethod(); //추상 메서드
}

위와 같이 추상 메서드 newMethod()를 추가하는 대신

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

이처럼 디폴트 메서드를 추가하면 기존의 MyInterface를 구현한 클래스를 변경하지 않아도 된다.(조상 클래스에 새로운 메서드를 추가한 것과 동일)

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

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");
    }
}

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

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

    }
}
output
method1() in Child3
method2() in Parent3
staticMethod() in MyInterface
staticMethod() in MyInterface2
profile
github blog 쓰다가 관리하기 귀찮아서 돌아왔다

0개의 댓글