자바 - 인터페이스

신범철·2022년 1월 14일
0

자바

목록 보기
4/17

머리말

이글은 자바를 빠르게 끝내기 위해 내가 헷갈리는 부분만을 정리해논 글입니다. Do it! 자바 프로그래밍 입문을 정리하지만 내용을 많이 건너뜀

인터페이스

인터페이스 정의

  • 인터페이스는 어떤 객체에 대한 명제로 이 객체가 어떤 메서드들을 제공하고 어떤 역할을 하는지에 대한 일종의 설명서로 대부분 설계단계에서 만들게 됩니다.
  • 인터페이스란 클래스의 일종으로 추상메서드와 상수로 이뤄진 클래스이다. -> 추상클래스에는 추상메서드와 일반메서드, 상수, 변수가 동시에 있을 수 있다는 점에서 다르다.
  • 추상메서드의 경우 구현부가 없는 메서드로만 구성되있기 때문에 new 생성자를 사용할 수 없고 그로 인해 인터페이스 안에 변수를 선언하더라도 힙메모리 안에 구성이 안되기 때문에 결국 상수로 변환된다.
  • 구현 코드가 없는 인터페이스의 상속은 타입상속이라고 불린다. -> 하위 클래스들의 타입의 역할을 하여 다형성을 구현할 수 있게 해주기 때문에

인터페이스 생성 및 구현

package interfaceex;

public interface Calc {
    double PI = 3.14;
    int ERROR = - 99999999;

    int add(int num1, int num2);
    int substract(int num1, int num2);
    int times(int num1, int num2);
    int divide(int num1,int num2);

}
  • 변수 PI와 ERROR는 실제 변수가 아닌 상수로 컴파일과정에서 public static final이 다 붙어서 상수화된다.

  • 계산기능을 하는 add, substract, times, divide메서드는 abstract키워드가 생략되었지만 이들은 구현부가 없는 추상메서드이다. 이 메서드들도 컴파일과정에서 추상 메서드로 변환된다.

  • 메서드를 함수의 이름, 매개변수 반환값을 선언할 수 있다는 것은 결국 이 메서드가 어떤 기능을 대략적으로 보여주는 기능을 한다. 인터페이스는 이렇게 객체의 기능을 간략하게 설명하는 기능을 하고 하위 클래스에서 어떻게 구현되야 하는 지를 안내한다.

package interfaceex;
//1번
public abstract class Calculator implements Calc {
    @Override
    public int add(int num1, int num2){
        return num1 + num2;
    }

    @Override
    public int substract(int num1, int num2){
        return num1 - num2;
    }
}
  • 1번 : Claculator 클래스를 만들고 인터페이스 Calc를 구현한다는 뜻에서 implements를 선언부에 사용합니다. 또한 Calc의 일부 메서드(add, substract)들만 구현할 것이기때문에 class 앞에 abstract을 붙혀 추상 클래스라고 선언해 줍니다.
package interfaceex;
//1번
public class CompleteCalc extends Calculator{
    @Override
    public int times(int num1, int num2){
        return num1 * num2;
    }

    @Override
    public int divide(int num1, int num2){
        if(num2 != 0)
            return num1/num2;
        else
            return Calc.ERROR;
    }

    public void showInfo(){
        System.out.println("Calc 인터페이스를 구현하였습니다.");
    }
}
  • 1번 : 클래스 선언부에 extends를 사용하여 상속 받는다는 것을 알리고 아래의 Calculator에서 구현하지 않은 나머지 2개의 메서드를 마저 작성한다.
  • CompleteCalc는 추상클래스가 아니기 때문에 showInfo와 같은 일반 메서드를 사용할 수 있다.
package interfaceex;

public class CalculatorTest {
    public static void main(String[] args) {
        int num1  =10;
        int num2 = 5;

        CompleteCalc calc = new CompleteCalc();
        System.out.println(calc.add(num1,num2));
        System.out.println(calc.substract(num1,num2));
        System.out.println(calc.times(num1,num2));
        System.out.println(calc.divide(num1, num2));
        calc.showInfo();
    }
}
  • CalculatorTest 클래스를 만들고 CompleteCalc클래스만 인스턴스를 생성할 수 있다. 왜 Why? Calculator클래스는 추상클래스이고, Calc인터페이스는 추상메서드만으로 선언했기 때문에 둘다 인스턴스를 생성할 수 없다.

  • CompleteCalc형으로 선언했지만 Calc나 Calculator로도 선언이 가능하다. 왜 why?
    상속관계에서 하위 클래스는 상위클래스의 자료형으로 업캐스팅(묵시적 형변환)이 되기 때문이다.

Calc calc = new CompleteCalc(); //가능
Calulator calc = new CompleteCalc(); //가능
CompleteCalc calc = new CompleteCalc(); //가능
CompleteCalc calc = new Calc(); //불가능
CompleteCalc calc = new Calulater(); //불가능

인터페이스의 역할

인터페이스는 프로그램에 어떤 메서드를 제공하는지 알려주는 명세 또는 약속의 역할을 한다.
그 이유는 어떤 객체가 어떤 인터페이스 타입이라는 것은 그 메서드들을 다 구현했다는 의미이기 때문이다. 그렇기 때문에 클라이언트 프로그램은 실제 모듈이나 클래스의 구현내용을 몰라도 인터페이스의 정의만 알면 그 객체를 사용할 수 있게 된다.

인터페이스와 다형성

인터페이스를 사용하면 다형성을 구현하여 확장성있는 프로그램을 만들 수 있다.
즉, 클라이언트 프로그램을 많이 수정하지 않고 기능을 추가하거나 다른 기능을 사용할 수 있다.

예제 시나리오

고객 센터에는 전화 상담을 하는 상담원들이 있습니다. 일단 고객 센터로 전화가 오면 대기열에 저장됩니다. 상담원이 지정되기 전까지 대기 상태가 됩니다. 각 전화를 상담원에게 배분하는 정책은 다음과 같이 여러 방식으로 구현할 수 있습니다.
- 순서대로 배분하기
- 짧은 대기열을 찾아 배분하기
- 우선순위가 높은(숙련도가 높은) 상담원에게 먼저 배분하기

위 배분 기능을 인터페이스로 구현하고 실행해보자

인터페이스를 구현할 때는 어떤 메서드가 필요한지 구상하고 관계도를 그려보는 단계가 필요한다.

package scheduler;

public interface Scheduler {
    public void getNextCall();
    public void sendCallToAgent();
}

인터페이스를 생성한것이다. 설계단계에서 이런 인터페이스를 잘 구현하는 것은 매우 어렵지만 중요한 단계이다.

package scheduler;

public class RoundRobin implements Scheduler{
    @Override
    public void getNextCall(){
        System.out.println("상담 전화를 순서대로 대기열에서 가져옵니다.");
    }

    @Override
    public void sendCallToAgent(){
        System.out.println("다음 순서의 상담원에게 배분합니다.");
    }

}
package scheduler;

public class LeastJob implements Scheduler{
    @Override
    public void getNextCall(){
        System.out.println("상담 전화를 순서대로 대기열에서 가져옵니다.");
    }

    @Override
    public void sendCallToAgent(){
        System.out.println("현재 상담업무가 없거나 상담대기가 가장 적은 상담원에게 할당");
    }

}
package scheduler;

public class PrioritAllocation implements Scheduler{
    @Override
    public void getNextCall(){
        System.out.println("고객등급이 높은 고객의 call을 먼저 가져옵니다.");
    }

    @Override
    public void sendCallToAgent(){
        System.out.println("업무 숙련도가 높은 상담원에게 먼저 배분합니다.");
    }
}

Scheduler 인터페이스를 각각의 방식으로 구현한 3가지 클래스이다.
다른 방식의 도입이 필요하다면 하나의 클래스를 더 만들어서 implement를 통해 언제든지 확장이 가능하다.

package scheduler;

import java.io.IOException;

public class SchedulerTest {
    public static void main(String[] args) throws IOException {
        System.out.println("전화 상담 할당 방식을 고르시오");
        System.out.println("R : 한명씩 차례로 할당");
        System.out.println("L : 쉬고 있거나 대기가 가장 적은 상담원에게 할당");
        System.out.println("P : 우선순위가 높은 고객 먼저 할당");

        int ch = System.in.read();
        Scheduler scheduler = null;
        if(ch =='R' || ch =='r'){
            scheduler = new RoundRobin();
        }
        else if(ch =='L' || ch =='l'){
            scheduler = new LeastJob();
        }
        else if(ch =='P' || ch =='p'){
            scheduler = new PrioritAllocation();
        }
        else{
            System.out.println("지원되지 않는 기능입니다.");
            return;
        }

        scheduler.getNextCall();
        scheduler.sendCallToAgent();
    }
}
  • 문자를 입력받고 if문을 통해 그 입력값에 맞는 클래스를 인스턴스를 생성한다.
  • 예제를 통해 인터페이스를 활용하여 다양한 정책이나 알고리즘을 프로그램의 큰 수정없이 적용하고 확장할 수 있음을 배웠다. 반대로 하나의 정책이나 알고리즘으로 구현되는 프로그램을 여러개의 정책이나 알고리즘을 선택할 수 있도록 인터페이스를 작성하는 경우도 있다.

인터페이스 요소

인터페이스 요소들

  • 상수 : 선언된 모든 매개변수는 컴파일시 상수로 처리된다.
  • 메서드 : 인터페이스의 모든 메서드는 추상 메서드이다.
  • 디폴트 메서드 : 구현코드가 없는 인터페이스에서 공통적으로 구현되야하는 메서드가 있는 경우 추상클래스의 구현메서드 처럼 기본적인 구현을 가지는 메서드이다. 기본 구현을 가지고 잇다고 해도 실제 구현하는 클래스에서 재정의 할수 있다.(default 키워드를 사용)
  • 정적 메서드 : static키워드가 붙은 메서드로 인스턴스 생성과 상관 없이 인터페이스 타입으로 호출이 가능한 메서드이다. 인스턴스를 사용하기 위해 클래스를 만들고 인스턴스를 생성하는 과정을 생략하고 바로 사용할 수 있게 구현해놓은 메서드
  • private 메서드 : 인터페이스 내에서만 사용가능한 메서드이고 디폴트 메서드나 정적메서드에 사용하기 위해 작성되는 메서드. 인터페이스를 구현해 놓은 클래스쪽에서 재정의하거나 사용할 수 없고 디폴트나 정적메서드를 통해서만 사용가능

요소 사용 예제

package interfaceex;

public interface Calc {
    double PI = 3.14;
    int ERROR = - 99999999;

    int add(int num1, int num2);
    int substract(int num1, int num2);
    int times(int num1, int num2);
    int divide(int num1,int num2);
    
    //디폴트 메서드
    default void description(){
        System.out.println("정수 계산기를 구현합니다.");
        myMethod(); //private 메서드 사용
    }
    
    //static  메서드
    static int total(int[] arr){
        int total = 0;
        
        for(int i :arr){
            total+= i;
        }
        mystaticMethod();//private static 메서드 사용
        return total;
    }
    
    //private 메서드
    private void myMethod(){
        System.out.println("private method");
    }
    
    //private static 메서드
    private static void mystaticMethod(){
        System.out.println("private static method");
    }
}
  • default 메서드 description()은 구현문을 가진 메서드로 implements하는 클래스들에게 기본적으로 적용되는 메서드
  • static메서드 total()은 인스턴스 생성없이 사용할 수 있는 메서드, 인터페이스명 타입으로 하여 실행가능
  • private메서드 myMethod는 인터페이스 내에서만 사용가능한 메서드 description()안에 사용함
  • private static메서드 mystaticMethod는 인터페이스 내에서만 사용가능하고 static키워드가 있기 때문에 static메서드 안에서만 사용 가능하다. total()안에서 사용함
public class CalculatorTest {
    public static void main(String[] args) {
        int num1  =10;
        int num2 = 5;

        CompleteCalc calc = new CompleteCalc();
        System.out.println(calc.add(num1,num2));
        System.out.println(calc.substract(num1,num2));
        System.out.println(calc.times(num1,num2));
        System.out.println(calc.divide(num1, num2));
        calc.showInfo();

        calc.description();

        int[] arr = {1,2,3,4,5};

        System.out.println(Calc.total(arr));
    }
}
//결과
15
5
50
2
Calc 인터페이스를 구현하였습니다.
정수 계산기를 구현합니다.
private method
private static method
15

인터페이스 활용하기

  • 인터페이스는 구현 코드가 없으므로 하나의 클래스가 여러 인터페이스를 구현할 수 있다.
  • 하지만 다른 인터페이스의 디폴트 메서드 이름이 중복되는 경우 에러가 발생하니 재정의해줄 필요가 있다.

한 클래스가 여러 인터페이스를 구현하는 경우

package interfaceex;

public interface Buy {
    void buy();
}

----------------

package interfaceex;

public interface Sell {
    void sell();
}

-----------------

package interfaceex;

public class Customer implements Buy,Sell{//Customer클래스는 Buy와 Sell인터페이스를 모두 구현 가능
    @Override
    public void sell(){
        System.out.println("구매하기");
    }
    @Override
    public void buy(){
        System.out.println("판매하기");
    }
}

-----------------

package interfaceex;

public class CustomerTest {
    public static void main(String[] args) {
        Customer customer = new Customer();

        Buy buyer = customer;//Buy 인터페이스형으로 형변환 Buy인터페이스의 메서드만 호출 가능
        buyer.buy();

        Sell seller = customer;Sell 인터페이스형으로 형변환 Sell인터페이스의 메서드만 호출 가능
        seller.sell();

        if(seller instanceof Customer){
            Customer customer2 = (Customer)seller;//seller를 하위 클래스형인 Customer로 다시 형 변환 buy,sell 둘다 사용가능
            customer2.buy();
            customer2.sell();
        }
    }
}

두 인터페이스의 디폴트 메서드가 중복 되는 경우

package interfaceex;

public interface Buy {
    void buy();

    default void order(){
        System.out.println("구매 주문");
    }
}

------------------

package interfaceex;

public interface Sell {
    void sell();

    default void order(){
        System.out.println("판매주문");
    }
}

-------------

package interfaceex;

public class Customer implements Buy,Sell{
    @Override
    public void sell(){
        System.out.println("구매하기");
    }
    @Override
    public void buy(){
        System.out.println("판매하기");
    }

    @Override
    public void order(){
        System.out.println("고객 판매 주문");
    }//디폴트 메서드인 order()를 Custom클래스에서 재정의
}

-------------------------

package interfaceex;

public class CustomerTest {
    public static void main(String[] args) {
        Customer customer = new Customer();

        Buy buyer = customer;
        buyer.buy();
        buyer.order();

        Sell seller = customer;
        seller.sell();
        seller.order();

        if(seller instanceof Customer){
            Customer customer2 = (Customer)seller;
            customer2.buy();
            customer2.sell();
        }
        customer.order();
    }
}

//결과
판매하기
고객 판매 주문
구매하기
고객 판매 주문
판매하기
구매하기
고객 판매 주문
  • 주의할점은 custimer가 Buy형으로 변환되고 buyer.order()를 호출하면 Buy에 구현한 디폴트 메서드가 아닌 Customer클래스에 재정의한 메서드가 호출된다는 사실이다. 이 이유는 메서드는 메서드 영역주소를 참조하여 메서드를 불러오는데 이를 가상 메서드라고 한다. 메서드 영역은 사용한 자료형(Buy)의 메서드가 아닌 생성된 인스턴스(Customer)의 메서드가 호출된다.

인터페이스 상속하기

  • 인터페이스 간에도 상속이 가능하다. 구현부가 없으므로 extends 뒤에 여러 인터페이스를 상속 받을 수 있다. 이런 인터페이스간의 상속을 타입 상속이라고 한다.

인터페이스 구현과 클래스 상속 함께 사용하기

인터페이스 구현과 클래스 상속을 함께 하는 경우에 대해 예제를 통해 알아보겠습니다.
BookSelf는 Shlef라는 클래스를 상속하고 Queue 인터페이스를 구현을 동시에 한다.

package interfaceex;

import java.util.ArrayList;

public class Shelf {
    protected ArrayList<String> shelf;//자료를 순서대로 저장할 Arraylist 선언

    public Shelf(){
        shelf = new ArrayList<String>();//디폴트 생성자로 Shelf 클래스를 생성하면
        //ArrayList도 생성됨
    }

    public ArrayList<String> getShelf(){
        return shelf;
    }
    public int getCount(){
        return shelf.size();
    }
}

------------------

package interfaceex;

public interface Queue {
    void enQueue(String title);//배열의 맨 마지막에 추가
    String deQueue();//배열의 맨 처음 항목 반환
    int getSize();//현재 큐에 있는 개수 반환
}

--------------------

package interfaceex;

public class BookShelf extends Shelf implements Queue{
    @Override
    public void enQueue(String title){
        shelf.add(title);//배열 요소 추가
    }

    @Override
    public String deQueue(){
        return shelf.remove(0);//맨 처음 요소를 배열에서 삭제하고 반환
    }

    @Override
    public int getSize(){
        return getCount();//배열 요소 갯수 반환
    }

}

---------------------

package interfaceex;

public class BookShelfTest {
    public static void main(String[] args) {
        Queue shelfQueue = new BookShelf();
        shelfQueue.enQueue("일번 1");//순서대로 요소 추가
        shelfQueue.enQueue("이번 2");
        shelfQueue.enQueue("삼번 3");

        System.out.println(shelfQueue.deQueue());//입력 순서대로 요소를 꺼내 출력
        System.out.println(shelfQueue.deQueue());
        System.out.println(shelfQueue.deQueue());
    }
}

//결과
일번 1
이번 2
삼번 3
  • Test 클래스에서 Bookshelf인스턴스를 생성하는데 자료형을 Bookshelf로 해도되고 Shelf로 해도되고 Queue로 해도 되지만 기능적이 면에서 Queue인터페이스를 타입으로 하고 인스턴스를 생성했다.
profile
https://github.com/beombu

0개의 댓글